Skip to content

Instantly share code, notes, and snippets.

@aneury1
Created January 15, 2026 09:27
Show Gist options
  • Select an option

  • Save aneury1/349b24fb8f5200b6a7b4c3db1ce662b4 to your computer and use it in GitHub Desktop.

Select an option

Save aneury1/349b24fb8f5200b6a7b4c3db1ce662b4 to your computer and use it in GitHub Desktop.
#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