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 <utility>
#include <vector>
#include <blt/iterator/enumerate.h>
#include <blt/logging/logging.h>
#include <blt/std/utility.h>
namespace detail
{
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>)
return "NULL";
else if constexpr (std::is_integral_v<Decay>)
@ -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 <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)...};
}
@ -109,10 +118,10 @@ public:
{}
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!");
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 (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>)
{
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
{
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 <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>
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<T>(type), index);
return column_binder_t{statement}.bind(type, index);
}
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:
template <typename... Types, size_t Indices>
auto bind_internal(std::index_sequence<Indices...>, Types&& types)
template <typename... Types, size_t... Indices>
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;
@ -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<std::string>& 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<std::string>& columns, std::vector<std::string>& primary_keys,
std::vector<detail::foreign_key_t>& 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<std::string>& columns;
std::vector<std::string>& primary_keys;
std::vector<detail::foreign_key_t>& foreign_keys;
std::vector<std::string> attributes;
std::string type;
std::string name;
@ -291,17 +313,54 @@ private:
class table_builder_t
{
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)}
{}
template<typename T>
table_column_builder_t& with_type(const std::string& name)
template <typename T>
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:
std::vector<std::string> columns{};
std::vector<std::string> primary_keys{};
std::vector<detail::foreign_key_t> 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:

View File

@ -19,6 +19,7 @@
#include "blt/gfx/renderer/batch_2d_renderer.h"
#include "blt/gfx/renderer/camera.h"
#include <imgui.h>
#include <sql.h>
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<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
{
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)