Created
January 15, 2026 09:27
-
-
Save aneury1/349b24fb8f5200b6a7b4c3db1ce662b4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <sqlite3.h> | |
| #include <iostream> | |
| #include <string> | |
| #include <vector> | |
| #include <memory> | |
| #include <sstream> | |
| #include <functional> | |
| #include <map> | |
| #include <type_traits> | |
| #include <tuple> | |
| // ============================================================================ | |
| // Type traits and helpers | |
| // ============================================================================ | |
| template<typename T> | |
| struct is_string : std::false_type {}; | |
| template<> | |
| struct is_string<std::string> : std::true_type {}; | |
| template<> | |
| struct is_string<const char*> : std::true_type {}; | |
| // SQL type mapping | |
| template<typename T> | |
| std::string getSqlType() { | |
| if constexpr (std::is_same_v<T, int> || std::is_same_v<T, long> || std::is_same_v<T, long long>) { | |
| return "INTEGER"; | |
| } else if constexpr (std::is_same_v<T, double> || std::is_same_v<T, float>) { | |
| return "REAL"; | |
| } else if constexpr (is_string<T>::value) { | |
| return "TEXT"; | |
| } else if constexpr (std::is_same_v<T, bool>) { | |
| return "INTEGER"; | |
| } | |
| return "TEXT"; | |
| } | |
| // ============================================================================ | |
| // Field Definition | |
| // ============================================================================ | |
| template<typename T> | |
| struct Field { | |
| std::string name; | |
| T* valuePtr; | |
| bool primaryKey; | |
| bool autoIncrement; | |
| bool notNull; | |
| bool unique; | |
| Field(const std::string& n, T* ptr) | |
| : name(n), valuePtr(ptr), primaryKey(false), | |
| autoIncrement(false), notNull(false), unique(false) {} | |
| Field& setPrimaryKey(bool val = true) { primaryKey = val; return *this; } | |
| Field& setAutoIncrement(bool val = true) { autoIncrement = val; return *this; } | |
| Field& setNotNull(bool val = true) { notNull = val; return *this; } | |
| Field& setUnique(bool val = true) { unique = val; return *this; } | |
| std::string getDefinition() const { | |
| std::string def = name + " " + getSqlType<T>(); | |
| if (primaryKey) def += " PRIMARY KEY"; | |
| if (autoIncrement) def += " AUTOINCREMENT"; | |
| if (notNull && !primaryKey) def += " NOT NULL"; | |
| if (unique && !primaryKey) def += " UNIQUE"; | |
| return def; | |
| } | |
| void bindValue(sqlite3_stmt* stmt, int index) const { | |
| if constexpr (std::is_same_v<T, int>) { | |
| sqlite3_bind_int(stmt, index, *valuePtr); | |
| } else if constexpr (std::is_same_v<T, long long>) { | |
| sqlite3_bind_int64(stmt, index, *valuePtr); | |
| } else if constexpr (std::is_same_v<T, double>) { | |
| sqlite3_bind_double(stmt, index, *valuePtr); | |
| } else if constexpr (std::is_same_v<T, std::string>) { | |
| sqlite3_bind_text(stmt, index, valuePtr->c_str(), -1, SQLITE_TRANSIENT); | |
| } else if constexpr (std::is_same_v<T, bool>) { | |
| sqlite3_bind_int(stmt, index, *valuePtr ? 1 : 0); | |
| } | |
| } | |
| void readValue(sqlite3_stmt* stmt, int index) { | |
| if constexpr (std::is_same_v<T, int>) { | |
| *valuePtr = sqlite3_column_int(stmt, index); | |
| } else if constexpr (std::is_same_v<T, long long>) { | |
| *valuePtr = sqlite3_column_int64(stmt, index); | |
| } else if constexpr (std::is_same_v<T, double>) { | |
| *valuePtr = sqlite3_column_double(stmt, index); | |
| } else if constexpr (std::is_same_v<T, std::string>) { | |
| const char* text = reinterpret_cast<const char*>(sqlite3_column_text(stmt, index)); | |
| *valuePtr = text ? text : ""; | |
| } else if constexpr (std::is_same_v<T, bool>) { | |
| *valuePtr = sqlite3_column_int(stmt, index) != 0; | |
| } | |
| } | |
| }; | |
| // ============================================================================ | |
| // Database Connection | |
| // ============================================================================ | |
| class Database { | |
| private: | |
| sqlite3* db; | |
| std::string dbPath; | |
| public: | |
| Database(const std::string& path) : db(nullptr), dbPath(path) {} | |
| ~Database() { | |
| close(); | |
| } | |
| bool open() { | |
| int rc = sqlite3_open(dbPath.c_str(), &db); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Cannot open database: " << sqlite3_errmsg(db) << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| void close() { | |
| if (db) { | |
| sqlite3_close(db); | |
| db = nullptr; | |
| } | |
| } | |
| sqlite3* getHandle() { return db; } | |
| bool execute(const std::string& sql) { | |
| char* errMsg = nullptr; | |
| int rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &errMsg); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "SQL error: " << errMsg << std::endl; | |
| sqlite3_free(errMsg); | |
| return false; | |
| } | |
| return true; | |
| } | |
| long long getLastInsertId() { | |
| return sqlite3_last_insert_rowid(db); | |
| } | |
| }; | |
| // ============================================================================ | |
| // Query Builder | |
| // ============================================================================ | |
| class QueryBuilder { | |
| private: | |
| std::string tableName; | |
| std::vector<std::string> selectFields; | |
| std::vector<std::string> whereConditions; | |
| std::vector<std::string> orderByFields; | |
| int limitValue; | |
| int offsetValue; | |
| public: | |
| QueryBuilder(const std::string& table) | |
| : tableName(table), limitValue(-1), offsetValue(0) {} | |
| QueryBuilder& select(const std::vector<std::string>& fields) { | |
| selectFields = fields; | |
| return *this; | |
| } | |
| QueryBuilder& where(const std::string& condition) { | |
| whereConditions.push_back(condition); | |
| return *this; | |
| } | |
| QueryBuilder& orderBy(const std::string& field, bool ascending = true) { | |
| orderByFields.push_back(field + (ascending ? " ASC" : " DESC")); | |
| return *this; | |
| } | |
| QueryBuilder& limit(int n) { | |
| limitValue = n; | |
| return *this; | |
| } | |
| QueryBuilder& offset(int n) { | |
| offsetValue = n; | |
| return *this; | |
| } | |
| std::string build() const { | |
| std::ostringstream query; | |
| query << "SELECT "; | |
| if (selectFields.empty()) { | |
| query << "*"; | |
| } else { | |
| for (size_t i = 0; i < selectFields.size(); ++i) { | |
| if (i > 0) query << ", "; | |
| query << selectFields[i]; | |
| } | |
| } | |
| query << " FROM " << tableName; | |
| if (!whereConditions.empty()) { | |
| query << " WHERE "; | |
| for (size_t i = 0; i < whereConditions.size(); ++i) { | |
| if (i > 0) query << " AND "; | |
| query << "(" << whereConditions[i] << ")"; | |
| } | |
| } | |
| if (!orderByFields.empty()) { | |
| query << " ORDER BY "; | |
| for (size_t i = 0; i < orderByFields.size(); ++i) { | |
| if (i > 0) query << ", "; | |
| query << orderByFields[i]; | |
| } | |
| } | |
| if (limitValue > 0) { | |
| query << " LIMIT " << limitValue; | |
| } | |
| if (offsetValue > 0) { | |
| query << " OFFSET " << offsetValue; | |
| } | |
| return query.str(); | |
| } | |
| }; | |
| // ============================================================================ | |
| // Model Base Class | |
| // ============================================================================ | |
| template<typename Derived> | |
| class Model { | |
| protected: | |
| Database* db; | |
| std::string tableName; | |
| std::vector<std::function<std::string()>> fieldDefinitions; | |
| std::vector<std::function<void(sqlite3_stmt*, int)>> fieldBinders; | |
| std::vector<std::function<void(sqlite3_stmt*, int)>> fieldReaders; | |
| std::vector<std::string> fieldNames; | |
| public: | |
| Model(Database* database, const std::string& table) | |
| : db(database), tableName(table) {} | |
| virtual ~Model() {} | |
| // Register field | |
| template<typename T> | |
| void registerField(Field<T>& field) { | |
| fieldNames.push_back(field.name); | |
| fieldDefinitions.push_back([&field]() { | |
| return field.getDefinition(); | |
| }); | |
| fieldBinders.push_back([&field](sqlite3_stmt* stmt, int index) { | |
| field.bindValue(stmt, index); | |
| }); | |
| fieldReaders.push_back([&field](sqlite3_stmt* stmt, int index) { | |
| field.readValue(stmt, index); | |
| }); | |
| } | |
| // Create table | |
| bool createTable() { | |
| std::ostringstream sql; | |
| sql << "CREATE TABLE IF NOT EXISTS " << tableName << " ("; | |
| for (size_t i = 0; i < fieldDefinitions.size(); ++i) { | |
| if (i > 0) sql << ", "; | |
| sql << fieldDefinitions[i](); | |
| } | |
| sql << ")"; | |
| std::cout << "Creating table: " << sql.str() << std::endl; | |
| return db->execute(sql.str()); | |
| } | |
| // Drop table | |
| bool dropTable() { | |
| std::string sql = "DROP TABLE IF EXISTS " + tableName; | |
| return db->execute(sql); | |
| } | |
| // Insert | |
| bool insert() { | |
| std::ostringstream sql; | |
| sql << "INSERT INTO " << tableName << " ("; | |
| // Field names (skip auto-increment primary key) | |
| bool first = true; | |
| std::vector<int> bindIndices; | |
| for (size_t i = 0; i < fieldNames.size(); ++i) { | |
| // Simple heuristic: skip first field if it's likely an ID | |
| if (i == 0 && fieldNames[i] == "id") continue; | |
| if (!first) sql << ", "; | |
| sql << fieldNames[i]; | |
| bindIndices.push_back(i); | |
| first = false; | |
| } | |
| sql << ") VALUES ("; | |
| first = true; | |
| for (size_t i = 0; i < bindIndices.size(); ++i) { | |
| if (!first) sql << ", "; | |
| sql << "?"; | |
| first = false; | |
| } | |
| sql << ")"; | |
| sqlite3_stmt* stmt; | |
| int rc = sqlite3_prepare_v2(db->getHandle(), sql.str().c_str(), -1, &stmt, nullptr); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Failed to prepare insert: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return false; | |
| } | |
| // Bind values | |
| int bindIndex = 1; | |
| for (int i : bindIndices) { | |
| fieldBinders[i](stmt, bindIndex++); | |
| } | |
| rc = sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| if (rc != SQLITE_DONE) { | |
| std::cerr << "Failed to insert: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Update by ID | |
| bool update(long long id) { | |
| std::ostringstream sql; | |
| sql << "UPDATE " << tableName << " SET "; | |
| // Skip ID field | |
| bool first = true; | |
| for (size_t i = 1; i < fieldNames.size(); ++i) { | |
| if (!first) sql << ", "; | |
| sql << fieldNames[i] << " = ?"; | |
| first = false; | |
| } | |
| sql << " WHERE " << fieldNames[0] << " = ?"; | |
| sqlite3_stmt* stmt; | |
| int rc = sqlite3_prepare_v2(db->getHandle(), sql.str().c_str(), -1, &stmt, nullptr); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Failed to prepare update: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return false; | |
| } | |
| // Bind values (skip ID field) | |
| int bindIndex = 1; | |
| for (size_t i = 1; i < fieldBinders.size(); ++i) { | |
| fieldBinders[i](stmt, bindIndex++); | |
| } | |
| // Bind ID for WHERE clause | |
| sqlite3_bind_int64(stmt, bindIndex, id); | |
| rc = sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| if (rc != SQLITE_DONE) { | |
| std::cerr << "Failed to update: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Delete by ID | |
| bool deleteById(long long id) { | |
| std::ostringstream sql; | |
| sql << "DELETE FROM " << tableName << " WHERE " << fieldNames[0] << " = ?"; | |
| sqlite3_stmt* stmt; | |
| int rc = sqlite3_prepare_v2(db->getHandle(), sql.str().c_str(), -1, &stmt, nullptr); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Failed to prepare delete: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return false; | |
| } | |
| sqlite3_bind_int64(stmt, 1, id); | |
| rc = sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| if (rc != SQLITE_DONE) { | |
| std::cerr << "Failed to delete: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return false; | |
| } | |
| return true; | |
| } | |
| // Find by ID | |
| bool findById(long long id) { | |
| std::ostringstream sql; | |
| sql << "SELECT * FROM " << tableName << " WHERE " << fieldNames[0] << " = ?"; | |
| sqlite3_stmt* stmt; | |
| int rc = sqlite3_prepare_v2(db->getHandle(), sql.str().c_str(), -1, &stmt, nullptr); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Failed to prepare select: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return false; | |
| } | |
| sqlite3_bind_int64(stmt, 1, id); | |
| rc = sqlite3_step(stmt); | |
| if (rc == SQLITE_ROW) { | |
| for (size_t i = 0; i < fieldReaders.size(); ++i) { | |
| fieldReaders[i](stmt, i); | |
| } | |
| sqlite3_finalize(stmt); | |
| return true; | |
| } | |
| sqlite3_finalize(stmt); | |
| return false; | |
| } | |
| // Find all | |
| std::vector<Derived> findAll() { | |
| std::vector<Derived> results; | |
| std::string sql = "SELECT * FROM " + tableName; | |
| sqlite3_stmt* stmt; | |
| int rc = sqlite3_prepare_v2(db->getHandle(), sql.c_str(), -1, &stmt, nullptr); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Failed to prepare select: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return results; | |
| } | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| Derived obj(db); | |
| for (size_t i = 0; i < obj.fieldReaders.size(); ++i) { | |
| obj.fieldReaders[i](stmt, i); | |
| } | |
| results.push_back(obj); | |
| } | |
| sqlite3_finalize(stmt); | |
| return results; | |
| } | |
| // Find with query builder | |
| std::vector<Derived> find(std::function<void(QueryBuilder&)> buildQuery) { | |
| std::vector<Derived> results; | |
| QueryBuilder qb(tableName); | |
| buildQuery(qb); | |
| std::string sql = qb.build(); | |
| sqlite3_stmt* stmt; | |
| int rc = sqlite3_prepare_v2(db->getHandle(), sql.c_str(), -1, &stmt, nullptr); | |
| if (rc != SQLITE_OK) { | |
| std::cerr << "Failed to prepare select: " << sqlite3_errmsg(db->getHandle()) << std::endl; | |
| return results; | |
| } | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| Derived obj(db); | |
| for (size_t i = 0; i < obj.fieldReaders.size(); ++i) { | |
| obj.fieldReaders[i](stmt, i); | |
| } | |
| results.push_back(obj); | |
| } | |
| sqlite3_finalize(stmt); | |
| return results; | |
| } | |
| // Count | |
| long long count() { | |
| std::string sql = "SELECT COUNT(*) FROM " + tableName; | |
| sqlite3_stmt* stmt; | |
| int rc = sqlite3_prepare_v2(db->getHandle(), sql.c_str(), -1, &stmt, nullptr); | |
| if (rc != SQLITE_OK) { | |
| return -1; | |
| } | |
| long long count = 0; | |
| if (sqlite3_step(stmt) == SQLITE_ROW) { | |
| count = sqlite3_column_int64(stmt, 0); | |
| } | |
| sqlite3_finalize(stmt); | |
| return count; | |
| } | |
| }; | |
| // ============================================================================ | |
| // Example Usage | |
| // ============================================================================ | |
| class User : public Model<User> { | |
| public: | |
| long long id; | |
| std::string name; | |
| std::string email; | |
| int age; | |
| bool active; | |
| Field<long long> idField; | |
| Field<std::string> nameField; | |
| Field<std::string> emailField; | |
| Field<int> ageField; | |
| Field<bool> activeField; | |
| User(Database* db) | |
| : Model(db, "users"), | |
| id(0), name(""), email(""), age(0), active(true), | |
| idField("id", &id), | |
| nameField("name", &name), | |
| emailField("email", &email), | |
| ageField("age", &age), | |
| activeField("active", &active) | |
| { | |
| idField.setPrimaryKey().setAutoIncrement(); | |
| nameField.setNotNull(); | |
| emailField.setNotNull().setUnique(); | |
| registerField(idField); | |
| registerField(nameField); | |
| registerField(emailField); | |
| registerField(ageField); | |
| registerField(activeField); | |
| } | |
| void print() const { | |
| std::cout << "User[id=" << id << ", name=" << name | |
| << ", email=" << email << ", age=" << age | |
| << ", active=" << (active ? "true" : "false") << "]" << std::endl; | |
| } | |
| }; | |
| int main() { | |
| // Create database | |
| Database db("test.db"); | |
| if (!db.open()) { | |
| return 1; | |
| } | |
| // Create user model | |
| User user(&db); | |
| // Drop and recreate table | |
| user.dropTable(); | |
| user.createTable(); | |
| std::cout << "\n=== CREATE (Insert) ===" << std::endl; | |
| // Insert users | |
| user.name = "Alice"; | |
| user.email = "alice@example.com"; | |
| user.age = 25; | |
| user.active = true; | |
| user.insert(); | |
| user.name = "Bob"; | |
| user.email = "bob@example.com"; | |
| user.age = 30; | |
| user.active = true; | |
| user.insert(); | |
| user.name = "Charlie"; | |
| user.email = "charlie@example.com"; | |
| user.age = 35; | |
| user.active = false; | |
| user.insert(); | |
| user.name = "Diana"; | |
| user.email = "diana@example.com"; | |
| user.age = 28; | |
| user.active = true; | |
| user.insert(); | |
| std::cout << "Inserted 4 users" << std::endl; | |
| std::cout << "\n=== READ (Find All) ===" << std::endl; | |
| auto allUsers = user.findAll(); | |
| for (const auto& u : allUsers) { | |
| u.print(); | |
| } | |
| std::cout << "\n=== READ (Find by ID) ===" << std::endl; | |
| User foundUser(&db); | |
| if (foundUser.findById(2)) { | |
| std::cout << "Found user with ID 2: "; | |
| foundUser.print(); | |
| } | |
| std::cout << "\n=== UPDATE ===" << std::endl; | |
| foundUser.name = "Bob Updated"; | |
| foundUser.age = 31; | |
| foundUser.update(2); | |
| foundUser.findById(2); | |
| std::cout << "Updated user: "; | |
| foundUser.print(); | |
| std::cout << "\n=== READ (Query Builder) ===" << std::endl; | |
| auto activeUsers = user.find([](QueryBuilder& qb) { | |
| qb.where("active = 1").orderBy("age", false).limit(2); | |
| }); | |
| std::cout << "Active users (ordered by age desc, limit 2):" << std::endl; | |
| for (const auto& u : activeUsers) { | |
| u.print(); | |
| } | |
| std::cout << "\n=== COUNT ===" << std::endl; | |
| std::cout << "Total users: " << user.count() << std::endl; | |
| std::cout << "\n=== DELETE ===" << std::endl; | |
| user.deleteById(3); | |
| std::cout << "Deleted user with ID 3" << std::endl; | |
| std::cout << "Total users after delete: " << user.count() << std::endl; | |
| std::cout << "\n=== Final List ===" << std::endl; | |
| allUsers = user.findAll(); | |
| for (const auto& u : allUsers) { | |
| u.print(); | |
| } | |
| db.close(); | |
| return 0; | |
| } | |
| /** | |
| g++ ORM.cpp -o sqlite_orm -lsqlite3 -std=c++17 | |
| ./sqlite_orm | |
| */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment