Brett 2025-07-10 01:23:50 -04:00
parent af2295963e
commit 474986c021
4 changed files with 154 additions and 29 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
cmake-build-*/
.env
*.env
*.sqlite3
*.sqlite

View File

@ -24,14 +24,16 @@
#include <string_view> #include <string_view>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <blt/iterator/enumerate.h>
#include <blt/logging/logging.h>
#include <blt/std/utility.h> #include <blt/std/utility.h>
namespace detail namespace detail
{ {
template <typename T> template <typename T>
std::string_view sql_name() std::string sql_name()
{ {
using Decay = std::decay_t<T> using Decay = std::decay_t<T>;
if constexpr (std::is_same_v<Decay, nullptr_t>) if constexpr (std::is_same_v<Decay, nullptr_t>)
return "NULL"; return "NULL";
else if constexpr (std::is_integral_v<Decay>) else if constexpr (std::is_integral_v<Decay>)
@ -45,6 +47,13 @@ namespace detail
else else
return "BLOB"; return "BLOB";
} }
struct foreign_key_t
{
std::string local_name;
std::string foreign_table;
std::string foreign_name;
};
} }
class column_t class column_t
@ -94,7 +103,7 @@ public:
private: private:
template <typename... Types, size_t... Indices> template <typename... Types, size_t... Indices>
[[nodiscard]] std::tuple<Types...> get_internal(std::index_sequence<Indices>) const [[nodiscard]] std::tuple<Types...> get_internal(std::index_sequence<Indices...>) const
{ {
return std::tuple<Types...>{get<Types>(Indices)...}; return std::tuple<Types...>{get<Types>(Indices)...};
} }
@ -109,10 +118,10 @@ public:
{} {}
template <typename T> template <typename T>
auto bind(T&& type, int col) int bind(const T& type, int col)
{ {
static_assert(!std::is_rvalue_reference_v<T>, "Lifetime of object must outlive its usage!"); static_assert(!std::is_rvalue_reference_v<T>, "Lifetime of object must outlive its usage!");
using Decay = std::decay<T>; using Decay = std::decay_t<T>;
if constexpr (std::is_same_v<Decay, bool> || std::is_integral_v<Decay>) if constexpr (std::is_same_v<Decay, bool> || std::is_integral_v<Decay>)
{ {
if constexpr (sizeof(Decay) == 8) if constexpr (sizeof(Decay) == 8)
@ -128,9 +137,12 @@ public:
} else if constexpr (std::is_same_v<Decay, std::string> || std::is_same_v<Decay, std::string_view>) } else if constexpr (std::is_same_v<Decay, std::string> || std::is_same_v<Decay, std::string_view>)
{ {
return sqlite3_bind_text(statement, col, type.data(), type.size(), nullptr); return sqlite3_bind_text(statement, col, type.data(), type.size(), nullptr);
} else if constexpr (std::is_same_v<Decay, char*> || std::is_same_v<Decay, const char*>)
{
return sqlite3_bind_text(statement, col, type, -1, nullptr);
} else } 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 * Indexes start at 1 for the left most template parameter
*/ */
template <typename T> template <typename T>
auto bind(T&& type, int col) int bind(const T& type, int col)
{ {
return column_binder_t{statement}.bind(std::forward<T>(type), col); return column_binder_t{statement}.bind(type, col);
} }
template <typename T> template <typename T>
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()); auto index = sqlite3_bind_parameter_index(statement, name.c_str());
return column_binder_t{statement}.bind(std::forward<T>(type), index); return column_binder_t{statement}.bind(type, index);
} }
template <typename... Types> template <typename... Types>
auto bind(Types&& types) int bind_all(const Types&... types)
{ {
return bind_internal<Types...>(std::index_sequence_for<Types...>(), std::forward<Types>(types)...); return bind_internal<Types...>(std::index_sequence_for<Types...>(), types...);
} }
private: private:
template <typename... Types, size_t Indices> template <typename... Types, size_t... Indices>
auto bind_internal(std::index_sequence<Indices...>, Types&& types) int bind_internal(std::index_sequence<Indices...>, const Types&... types)
{ {
return ((bind<Types>(Indices, std::forward<Types>(types)) != SQLITE_OK) || ...); return ((bind<Types>(types, Indices + 1) != SQLITE_OK) | ...);
} }
sqlite3_stmt* statement; sqlite3_stmt* statement;
@ -195,13 +207,13 @@ public:
return *this; return *this;
} }
binder_t bind() const binder_t bind() const // NOLINT
{ {
sqlite3_reset(statement); sqlite3_reset(statement);
return binder_t{statement}; return binder_t{statement};
} }
[[nodiscard]] bool execute() const; [[jetbrains::has_side_effects]] bool execute() const; // NOLINT
[[nodiscard]] column_t fetch() const [[nodiscard]] column_t fetch() const
{ {
@ -220,15 +232,17 @@ class table_builder_t;
class table_column_builder_t class table_column_builder_t
{ {
public: public:
table_column_builder_t(table_builder_t& parent, std::vector<std::string>& columns, std::string type, std::string name): parent{parent}, table_column_builder_t(table_builder_t& parent, std::vector<std::string>& columns, std::vector<std::string>& primary_keys,
columns{columns}, std::vector<detail::foreign_key_t>& foreign_keys, std::string type, std::string name): parent{parent}, columns{columns},
type{std::move(type)}, primary_keys{primary_keys},
name{std::move(name)} foreign_keys{foreign_keys},
type{std::move(type)},
name{std::move(name)}
{} {}
table_column_builder_t& primary_key() table_column_builder_t& primary_key()
{ {
attributes.emplace_back("PRIMARY KEY"); primary_keys.push_back(name);
return *this; return *this;
} }
@ -253,6 +267,12 @@ public:
return *this; 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() table_builder_t& finish()
{ {
if (!created) if (!created)
@ -281,8 +301,10 @@ private:
} }
bool created = false; bool created = false;
table_builder_t& parent table_builder_t& parent;
std::vector<std::string>& columns; std::vector<std::string>& columns;
std::vector<std::string>& primary_keys;
std::vector<detail::foreign_key_t>& foreign_keys;
std::vector<std::string> attributes; std::vector<std::string> attributes;
std::string type; std::string type;
std::string name; std::string name;
@ -291,17 +313,54 @@ private:
class table_builder_t class table_builder_t
{ {
public: public:
template <typename>
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)} explicit table_builder_t(sqlite3* db, std::string name): db{db}, name{std::move(name)}
{} {}
template<typename T> template <typename T>
table_column_builder_t& with_type(const std::string& name) table_column_builder_t with_column(const std::string& name)
{ {
return table_column_builder_t{*this, columns, detail::sql_name<T>(), name}; return table_column_builder_t{*this, columns, primary_keys, foreign_keys, detail::sql_name<T>(), name};
} }
template <typename... Types>
table_builder_t& with_columns(required_string_t<Types>... names)
{
(with_column<Types>(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: private:
std::vector<std::string> columns{}; std::vector<std::string> columns{};
std::vector<std::string> primary_keys{};
std::vector<detail::foreign_key_t> foreign_keys{};
sqlite3* db; sqlite3* db;
std::string name{}; std::string name{};
}; };
@ -312,7 +371,7 @@ public:
explicit statement_builder_t(sqlite3* db): db{db} 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}; return table_builder_t{db, name};
} }
@ -349,6 +408,11 @@ public:
return statement_builder_t{db}; return statement_builder_t{db};
} }
[[nodiscard]] auto get_error() const
{
return sqlite3_errmsg(db);
}
~database_t(); ~database_t();
private: private:

View File

@ -19,6 +19,7 @@
#include "blt/gfx/renderer/batch_2d_renderer.h" #include "blt/gfx/renderer/batch_2d_renderer.h"
#include "blt/gfx/renderer/camera.h" #include "blt/gfx/renderer/camera.h"
#include <imgui.h> #include <imgui.h>
#include <sql.h>
blt::gfx::matrix_state_manager global_matrices; blt::gfx::matrix_state_manager global_matrices;
blt::gfx::resource_manager resources; blt::gfx::resource_manager resources;
@ -56,5 +57,25 @@ void destroy(const blt::gfx::window_data&)
int main() 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<float>("meep").not_null().primary_key();
silly_table.with_column<int>("merow").unique();
silly_table.with_column<std::string>("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;
} }

View File

@ -26,7 +26,8 @@ statement_t::statement_t(sqlite3* db, const std::string& stmt): db{db}
bool statement_t::execute() const 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() statement_t::~statement_t()
@ -34,6 +35,40 @@ statement_t::~statement_t()
sqlite3_finalize(statement); 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) database_t::database_t(const std::string& file)
{ {
if (sqlite3_open(file.c_str(), &db) != SQLITE_OK) if (sqlite3_open(file.c_str(), &db) != SQLITE_OK)