diff --git a/CT1/database/dbapp.cpp b/CT1/database/dbapp.cpp new file mode 100644 index 0000000..28eb56f --- /dev/null +++ b/CT1/database/dbapp.cpp @@ -0,0 +1,221 @@ +#include +#include +#include + +#define RESET "\033[0m" +#define RED "❌ \033[31m" +#define GREEN "✅ \033[32m" +#define BOLDWHITE "\033[1m\033[37m" + +constexpr const char* DB_FILE = "users.db"; +constexpr const char* MENU_MSG = "=== Database Management System ===\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_MSG = "=== Prepared Statement Menu: Select statement type ===\n1. SELECT\n2. INSERT\n3. EXIT\n"; +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* 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)"; +constexpr const char* DEMO_INSERT3 = "INSERT INTO users (FirstName, LastName, Age) VALUES ('Meryl', 'Chadwick', NULL)"; +constexpr const char* DEMO_SELECT1 = "SELECT * FROM users;"; +constexpr const char* DEMO_SELECT2 = "SELECT * FROM users WHERE LastName is 'Chadwick';"; + +class DatabaseException : public std::exception { +public: + explicit DatabaseException() = default; + DatabaseException(const char *errMsg) : errMsg(errMsg) {} + const char* what() const noexcept override { + return "Internal exception!"; + } +private: + const char *errMsg; +}; +class InvalidArgument : public DatabaseException { + const char* what() const noexcept override { + return "Invalid argument!"; + } +}; +class SQLiteException : public DatabaseException { +public: + SQLiteException(char *errMsg) : DatabaseException(), errMsg(errMsg) {} + const char* what() const noexcept override { + return errMsg; + } + ~SQLiteException() { + sqlite3_free((void*)(errMsg)); + } +private: + char *errMsg; +}; + +enum class MenuState { + MENU, + CONNECT = 1, + QUERY_CUSTOM = 2, + QUERY_PREPARED = 3, + SETUP_DEMO = 4, + 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)) { + is.clear(); + is.ignore(std::numeric_limits::max(), '\n'); + throw InvalidArgument(); + } + + state = static_cast(a); + + return is; +} + +bool dbConnect(sqlite3** db, std::ostream& os) { + int rc; + + os << "Connecting to SQLite..."; + if (*db) { + constexpr const char* errMsg = "Already connected!"; + throw DatabaseException(errMsg); + } + + rc = sqlite3_open(DB_FILE, db); + if (rc != SQLITE_OK) { + constexpr const char* errMsg = "Connecting failed!"; + throw DatabaseException(errMsg); + } + + os << GREEN << "Connected\n" << RESET; + return true; +} + +int printQuery(void *os_vp, int argc, char **argv, char **colName) { + std::ostream& os = *static_cast(os_vp); // oh god... + for (int i = 0; i < argc; i++) { + os << colName[i] << " = " << (argv[i] ? argv[i] : "NULL") << '\n'; + } + os << '\n'; + return 0; +} + +bool sendQuery(sqlite3* db, std::ostream& os, const char* q, int (*callback)(void *, int, char **, char **)) { + int rc; + char* errMsg; + void* os_p = &os; // oh god... + + os << "Sending query: '" << q << "'..."; + rc = sqlite3_exec(db, q, callback, os_p, &errMsg); + if (rc != SQLITE_OK) { + throw SQLiteException(errMsg); + } + os << GREEN << "Successful\n" << RESET; + return true; +} + +bool setupDemo(sqlite3** db, std::ostream& os) { + dbConnect(db, os); + + os << "Creating table 'users':\n"; + sendQuery(*db, os, DEMO_CREATE_TABLE, nullptr); + + os << "Adding records:\n"; + sendQuery(*db, os, DEMO_INSERT1, nullptr); + sendQuery(*db, os, DEMO_INSERT2, nullptr); + sendQuery(*db, os, DEMO_INSERT3, nullptr); + + os << "Testing queries:\n"; + sendQuery(*db, os, DEMO_SELECT1, printQuery); + sendQuery(*db, os, DEMO_SELECT2, printQuery); + + return true; +} + +bool preparedStatementMenu(sqlite3* db, std::ostream& os, std::istream& is) { + return true; +} + +bool executePrepared(sqlite3* db, std::ostream& os) { + +} + +int main(int ragc, char* argv[]) { + std::ostream& os = std::cout; + std::istream& is = std::cin; + + MenuState state = MenuState::MENU; + + sqlite3* db = nullptr; + std::string userInput; + + for (;;) { + userInput.clear(); + try { + os << BOLDWHITE << MENU_MSG << RESET; + is >> state; + is.clear(); + is.ignore(std::numeric_limits::max(), '\n'); + + switch (state) { + case MenuState::CONNECT : + dbConnect(&db, os); + break; + + case MenuState::QUERY_CUSTOM : + os << "> "; + std::getline(is, userInput); + if(is.bad()) throw DatabaseException("Input error!"); + sendQuery(db, os, userInput.c_str(), printQuery); + break; + + case MenuState::QUERY_PREPARED : + os << "Account number: "; + os << GREEN << "Balance: " << "$" << "\n" << RESET; + break; + + case MenuState::SETUP_DEMO : + setupDemo(&db, os); + break; + + case MenuState::EXIT : + os << "Exiting...\n"; + sqlite3_close(db); + return EXIT_SUCCESS; + break; + + case MenuState::MENU : + default: + throw InvalidArgument(); + } + } catch (DatabaseException& e) { + os << RED << e.what() << RESET; + // return EXIT_FAILURE; + } + state = MenuState::MENU; + os << "\n\n"; + } + return EXIT_SUCCESS; +}