diff --git a/CT1/database/Makefile b/CT1/database/Makefile index cebe2ab..1afa876 100644 --- a/CT1/database/Makefile +++ b/CT1/database/Makefile @@ -1,5 +1,5 @@ CXX = g++ -CXXFLAGS = -std=c++20 -Wall -pedantic -fsanitize=address -g +CXXFLAGS = -std=c++20 -Wall -pedantic -O2 LDFLAGS = -lsqlite3 SRCS = $(wildcard *.cpp) diff --git a/CT1/database/dbapp.cpp b/CT1/database/dbapp.cpp index 28eb56f..83cd643 100644 --- a/CT1/database/dbapp.cpp +++ b/CT1/database/dbapp.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include #define RESET "\033[0m" #define RED "❌ \033[31m" @@ -13,6 +15,8 @@ constexpr const char* MENU_PREP_MSG = "=== Prepared Statement Menu: Select state constexpr const char* MENU_PREP_COL_MSG = "=== Prepared Statement Menu: Select filter culumn ===\n1. Connect to database\n2. Execute custom SQL query\n3. Execute prepared SQL query\n4. Setup demo database\n5. Exit\n"; constexpr const char* MENU_PREP_TYPE_MSG = "=== Prepared Statement Menu: Select filter type ===\n1. Connect to database\n2. Execute custom SQL query\n3. Execute prepared SQL query\n4. Setup demo database\n5. Exit\n"; +constexpr const char* PREP_STATEMENT = "INSERT INTO users (FirstName, LastName, Age) VALUES (?, ?, ?)"; + constexpr const char* DEMO_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS users (ID INTEGER PRIMARY KEY AUTOINCREMENT, FirstName varchar(255) NOT NULL, LastName varchar(255) NOT NULL, Age int);"; constexpr const char* DEMO_INSERT1 = "INSERT INTO users (FirstName, LastName, Age) VALUES ('Tedd', 'Chadwick', 23)"; constexpr const char* DEMO_INSERT2 = "INSERT INTO users (FirstName, LastName, Age) VALUES ('Kathe', 'Claris', 31)"; @@ -22,10 +26,10 @@ constexpr const char* DEMO_SELECT2 = "SELECT * FROM users WHERE LastName is 'Cha class DatabaseException : public std::exception { public: - explicit DatabaseException() = default; + explicit DatabaseException() : errMsg(nullptr) {} DatabaseException(const char *errMsg) : errMsg(errMsg) {} const char* what() const noexcept override { - return "Internal exception!"; + return errMsg ? errMsg : "Internal error!"; } private: const char *errMsg; @@ -57,30 +61,6 @@ enum class MenuState { EXIT = 5, }; -enum class StatementMenuState { - MENU, - SELECT = 1, - INSERT = 2, - EXIT = 3, -}; - -enum class ColumnMenuState { - MENU, - FIRST_NAME = 1, - SECOND_NAME = 2, - AGE = 3, - EXIT = 4, -}; - -enum class FilterMenuState { - MENU, - IS = 1, - IS_NOT = 2, - LESS = 3, - GREATER = 4, - EXIT = 5, -}; - std::istream& operator>>(std::istream& is, MenuState& state) { int a; if (!(is >> a)) { @@ -127,17 +107,17 @@ bool sendQuery(sqlite3* db, std::ostream& os, const char* q, int (*callback)(voi char* errMsg; void* os_p = &os; // oh god... - os << "Sending query: '" << q << "'..."; + os << "Sending query: '" << q << "'\n"; rc = sqlite3_exec(db, q, callback, os_p, &errMsg); if (rc != SQLITE_OK) { throw SQLiteException(errMsg); } - os << GREEN << "Successful\n" << RESET; + os << GREEN << "Successful\n\n" << RESET; return true; } bool setupDemo(sqlite3** db, std::ostream& os) { - dbConnect(db, os); + if (!*db) dbConnect(db, os); os << "Creating table 'users':\n"; sendQuery(*db, os, DEMO_CREATE_TABLE, nullptr); @@ -154,12 +134,38 @@ bool setupDemo(sqlite3** db, std::ostream& os) { return true; } -bool preparedStatementMenu(sqlite3* db, std::ostream& os, std::istream& is) { - return true; -} +bool executePrepared(sqlite3* db, std::ostream& os, std::istream& is) { + sqlite3_stmt *ppStmt; + int rc; -bool executePrepared(sqlite3* db, std::ostream& os) { + std::string fn; + std::string ln; + int age; + os << "First name: "; + is >> fn; if (std::any_of(fn.begin(), fn.end(), [](auto c){ return !std::isalpha(c); })) throw DatabaseException("Invalid name!"); + os << "Last name: "; + is >> ln; if (std::any_of(ln.begin(), ln.end(), [](auto c){ return !std::isalpha(c); })) throw DatabaseException("Invalid name!"); + os << "Age (-1 for NULL): "; + if (!(is >> age)) age = -1; + + os << "Executing prepared statement: " << PREP_STATEMENT << "\nwith values: " + << fn << ", " << ln << ", " << (age >= 0 ? std::to_string(age) : std::string("NULL")) << "\n"; + rc = sqlite3_prepare(db, PREP_STATEMENT, 1024, &ppStmt, nullptr); + if (rc != SQLITE_OK) throw DatabaseException("Statement cant be prepared!"); + + sqlite3_bind_text(ppStmt, 1, fn.c_str(), -1, SQLITE_STATIC); + sqlite3_bind_text(ppStmt, 2, ln.c_str(), -1, SQLITE_STATIC); + if (age >= 0) sqlite3_bind_int(ppStmt, 3, age); + + rc = sqlite3_step(ppStmt); + if (rc != SQLITE_DONE) { + throw DatabaseException(sqlite3_errmsg(db)); + } + + rc = sqlite3_finalize(ppStmt); + if (rc != SQLITE_OK) throw DatabaseException("Statement failed!"); + return rc; } int main(int ragc, char* argv[]) { @@ -192,8 +198,8 @@ int main(int ragc, char* argv[]) { break; case MenuState::QUERY_PREPARED : - os << "Account number: "; - os << GREEN << "Balance: " << "$" << "\n" << RESET; + executePrepared(db, os, is); + os << "Done\n"; break; case MenuState::SETUP_DEMO : @@ -211,11 +217,11 @@ int main(int ragc, char* argv[]) { throw InvalidArgument(); } } catch (DatabaseException& e) { - os << RED << e.what() << RESET; + os << RED << e.what() << "\n" << RESET; // return EXIT_FAILURE; } state = MenuState::MENU; - os << "\n\n"; + os << "\n"; } return EXIT_SUCCESS; }