diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2d62de65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +cmake-build-*/ +.env +*.env +*.sqlite3 +*.sqlite diff --git a/include/sql.h b/include/sql.h index 7a398c9b..9f416b28 100644 --- a/include/sql.h +++ b/include/sql.h @@ -24,14 +24,16 @@ #include #include #include +#include +#include #include namespace detail { template - std::string_view sql_name() + std::string sql_name() { - using Decay = std::decay_t + using Decay = std::decay_t; if constexpr (std::is_same_v) return "NULL"; else if constexpr (std::is_integral_v) @@ -45,6 +47,13 @@ namespace detail else return "BLOB"; } + + struct foreign_key_t + { + std::string local_name; + std::string foreign_table; + std::string foreign_name; + }; } class column_t @@ -94,7 +103,7 @@ public: private: template - [[nodiscard]] std::tuple get_internal(std::index_sequence) const + [[nodiscard]] std::tuple get_internal(std::index_sequence) const { return std::tuple{get(Indices)...}; } @@ -109,10 +118,10 @@ public: {} template - auto bind(T&& type, int col) + int bind(const T& type, int col) { static_assert(!std::is_rvalue_reference_v, "Lifetime of object must outlive its usage!"); - using Decay = std::decay; + using Decay = std::decay_t; if constexpr (std::is_same_v || std::is_integral_v) { if constexpr (sizeof(Decay) == 8) @@ -128,9 +137,12 @@ public: } else if constexpr (std::is_same_v || std::is_same_v) { return sqlite3_bind_text(statement, col, type.data(), type.size(), nullptr); + } else if constexpr (std::is_same_v || std::is_same_v) + { + return sqlite3_bind_text(statement, col, type, -1, nullptr); } else { - return sqlite3_bind_blob64(statement, col, type.data(), type.size()); + return sqlite3_bind_blob64(statement, col, type.data(), type.data(), nullptr); } } @@ -148,29 +160,29 @@ public: * Indexes start at 1 for the left most template parameter */ template - auto bind(T&& type, int col) + int bind(const T& type, int col) { - return column_binder_t{statement}.bind(std::forward(type), col); + return column_binder_t{statement}.bind(type, col); } template - auto bind(T&& type, const std::string& name) + int bind(const T& type, const std::string& name) { auto index = sqlite3_bind_parameter_index(statement, name.c_str()); - return column_binder_t{statement}.bind(std::forward(type), index); + return column_binder_t{statement}.bind(type, index); } template - auto bind(Types&& types) + int bind_all(const Types&... types) { - return bind_internal(std::index_sequence_for(), std::forward(types)...); + return bind_internal(std::index_sequence_for(), types...); } private: - template - auto bind_internal(std::index_sequence, Types&& types) + template + int bind_internal(std::index_sequence, const Types&... types) { - return ((bind(Indices, std::forward(types)) != SQLITE_OK) || ...); + return ((bind(types, Indices + 1) != SQLITE_OK) | ...); } sqlite3_stmt* statement; @@ -195,13 +207,13 @@ public: return *this; } - binder_t bind() const + binder_t bind() const // NOLINT { sqlite3_reset(statement); return binder_t{statement}; } - [[nodiscard]] bool execute() const; + [[jetbrains::has_side_effects]] bool execute() const; // NOLINT [[nodiscard]] column_t fetch() const { @@ -220,15 +232,17 @@ class table_builder_t; class table_column_builder_t { public: - table_column_builder_t(table_builder_t& parent, std::vector& columns, std::string type, std::string name): parent{parent}, - columns{columns}, - type{std::move(type)}, - name{std::move(name)} + table_column_builder_t(table_builder_t& parent, std::vector& columns, std::vector& primary_keys, + std::vector& foreign_keys, std::string type, std::string name): parent{parent}, columns{columns}, + primary_keys{primary_keys}, + foreign_keys{foreign_keys}, + type{std::move(type)}, + name{std::move(name)} {} table_column_builder_t& primary_key() { - attributes.emplace_back("PRIMARY KEY"); + primary_keys.push_back(name); return *this; } @@ -253,6 +267,12 @@ public: return *this; } + table_column_builder_t& foreign_key(const std::string& table, const std::string& column) + { + foreign_keys.emplace_back(detail::foreign_key_t{name, table, column}); + return *this; + } + table_builder_t& finish() { if (!created) @@ -281,8 +301,10 @@ private: } bool created = false; - table_builder_t& parent + table_builder_t& parent; std::vector& columns; + std::vector& primary_keys; + std::vector& foreign_keys; std::vector attributes; std::string type; std::string name; @@ -291,17 +313,54 @@ private: class table_builder_t { public: + template + struct required_string_t + { + std::string name; + + required_string_t(std::string name): name{std::move(name)} // NOLINT + {} + + operator std::string() // NOLINT + { + return name; + } + }; + explicit table_builder_t(sqlite3* db, std::string name): db{db}, name{std::move(name)} {} - template - table_column_builder_t& with_type(const std::string& name) + template + table_column_builder_t with_column(const std::string& name) { - return table_column_builder_t{*this, columns, detail::sql_name(), name}; + return table_column_builder_t{*this, columns, primary_keys, foreign_keys, detail::sql_name(), name}; } + template + table_builder_t& with_columns(required_string_t... names) + { + (with_column(names.name), ...); + return *this; + } + + table_builder_t& with_foreign_key(const std::string& local_name, const std::string& foreign_table, const std::string& foreign_name) + { + foreign_keys.emplace_back(detail::foreign_key_t{local_name, foreign_table, foreign_name}); + return *this; + } + + table_builder_t& with_primary_key(const std::string& name) + { + primary_keys.push_back(name); + return *this; + } + + statement_t build(); + private: std::vector columns{}; + std::vector primary_keys{}; + std::vector foreign_keys{}; sqlite3* db; std::string name{}; }; @@ -312,7 +371,7 @@ public: explicit statement_builder_t(sqlite3* db): db{db} {} - table_builder_t create_table(const std::string& name) + table_builder_t create_table(const std::string& name) const { return table_builder_t{db, name}; } @@ -349,6 +408,11 @@ public: return statement_builder_t{db}; } + [[nodiscard]] auto get_error() const + { + return sqlite3_errmsg(db); + } + ~database_t(); private: diff --git a/src/main.cpp b/src/main.cpp index 19c4eb49..882f33cd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "blt/gfx/renderer/batch_2d_renderer.h" #include "blt/gfx/renderer/camera.h" #include +#include blt::gfx::matrix_state_manager global_matrices; blt::gfx::resource_manager resources; @@ -56,5 +57,25 @@ void destroy(const blt::gfx::window_data&) int main() { - blt::gfx::init(blt::gfx::window_data{"Minecraft Color Picker", init, update, destroy}.setSyncInterval(1)); + // blt::gfx::init(blt::gfx::window_data{"Minecraft Color Picker", init, update, destroy}.setSyncInterval(1)); + database_t db("test.db"); + auto silly_table = db.builder().create_table("silly"); + silly_table.with_column("meep").not_null().primary_key(); + silly_table.with_column("merow").unique(); + silly_table.with_column("meow").primary_key(); + const auto silly_table_statement = silly_table.build(); + silly_table_statement.execute(); + + const auto sql = "INSERT INTO silly (meep, merow, meow) VALUES (?, ?, ?)"; + const auto insert_statement = db.prepare(sql); + static auto meow2 = "meow1"; + for (int i = 0; i < 10; i++) + { + auto str = meow2 + std::to_string(i); + auto values = insert_statement.bind(); + values.bind_all(0.5, i, str); + insert_statement.execute(); + } + + return 0; } \ No newline at end of file diff --git a/src/sql.cpp b/src/sql.cpp index 7084299e..28dade56 100644 --- a/src/sql.cpp +++ b/src/sql.cpp @@ -26,7 +26,8 @@ statement_t::statement_t(sqlite3* db, const std::string& stmt): db{db} bool statement_t::execute() const { - return sqlite3_step(statement) == SQLITE_OK; + const auto v = sqlite3_step(statement); + return v == SQLITE_OK || v == SQLITE_DONE; } statement_t::~statement_t() @@ -34,6 +35,40 @@ statement_t::~statement_t() sqlite3_finalize(statement); } +statement_t table_builder_t::build() +{ + std::string sql = "CREATE TABLE IF NOT EXISTS "; + sql += name; + sql += " ("; + for (const auto& [i, column] : blt::enumerate(columns)) + { + sql += column; + if (i != columns.size() - 1) + sql += ", "; + } + if (!primary_keys.empty()) + { + sql += ", PRIMARY KEY ("; + for (const auto& [i, key] : blt::enumerate(primary_keys)) + { + sql += key; + if (i != primary_keys.size() - 1) + sql += ", "; + } + sql += ")"; + } + if (!foreign_keys.empty()) + { + for (const auto& [i, key] : blt::enumerate(foreign_keys)) + { + sql += ", FOREIGN KEY ("; + sql += key.local_name + " REFERENCES " + key.foreign_table + "(" + key.foreign_name + ")"; + } + } + sql += ");"; + return statement_t{db, sql}; +} + database_t::database_t(const std::string& file) { if (sqlite3_open(file.c_str(), &db) != SQLITE_OK)