minecraft-color-picker/include/sql.h

423 lines
9.7 KiB
C++

#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef SQL_H
#define SQL_H
#include <sqlite3.h>
#include <string>
#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 sql_name()
{
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>)
return "INTEGER";
else if constexpr (std::is_floating_point_v<Decay>)
return "REAL";
else if constexpr (std::is_same_v<Decay, std::string> || std::is_same_v<Decay, std::string_view> || std::is_same_v<Decay, char*>)
return "TEXT";
else if constexpr (std::is_same_v<Decay, bool>)
return "BOOLEAN";
else
return "BLOB";
}
struct foreign_key_t
{
std::string local_name;
std::string foreign_table;
std::string foreign_name;
};
}
class column_t
{
public:
explicit column_t(sqlite3_stmt* statement): statement{statement}
{}
template <typename T>
auto get(const int col) const -> decltype(auto)
{
using Decay = std::decay_t<T>;
if constexpr (std::is_integral_v<Decay>)
{
if constexpr (sizeof(Decay) == 8)
{
return sqlite3_column_int64(statement, col);
} else
{
return sqlite3_column_int(statement, col);
}
} else if constexpr (std::is_same_v<Decay, double>)
{
return sqlite3_column_double(statement, col);
} else if constexpr (std::is_same_v<Decay, float>)
{
return static_cast<float>(sqlite3_column_double(statement, col));
} else if constexpr (std::is_same_v<Decay, std::string> || std::is_same_v<Decay, std::string_view> || std::is_same_v<Decay, char*>)
{
return T(sqlite3_column_text(statement, col));
} else
{
return static_cast<T>(sqlite3_column_blob(statement, col));
}
}
template <typename... Types>
[[nodiscard]] std::tuple<Types...> get() const
{
return get_internal<Types...>(std::index_sequence_for<Types...>());
}
[[nodiscard]] size_t size(const int col) const
{
return static_cast<size_t>(sqlite3_column_bytes(statement, col));
}
private:
template <typename... Types, size_t... Indices>
[[nodiscard]] std::tuple<Types...> get_internal(std::index_sequence<Indices...>) const
{
return std::tuple<Types...>{get<Types>(Indices)...};
}
sqlite3_stmt* statement;
};
class column_binder_t
{
public:
explicit column_binder_t(sqlite3_stmt* statement): statement{statement}
{}
template <typename T>
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<T>;
if constexpr (std::is_same_v<Decay, bool> || std::is_integral_v<Decay>)
{
if constexpr (sizeof(Decay) == 8)
return sqlite3_bind_int64(statement, col, type);
else
return sqlite3_bind_int(statement, col, type);
} else if constexpr (std::is_floating_point_v<Decay>)
{
return sqlite3_bind_double(statement, col, type);
} else if constexpr (std::is_same_v<Decay, nullptr_t>)
{
return sqlite3_bind_null(statement, col);
} 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.data(), nullptr);
}
}
private:
sqlite3_stmt* statement;
};
class binder_t
{
public:
explicit binder_t(sqlite3_stmt* statement): statement(statement)
{}
/**
* Indexes start at 1 for the left most template parameter
*/
template <typename T>
int bind(const T& type, int col)
{
return column_binder_t{statement}.bind(type, col);
}
template <typename T>
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(type, index);
}
template <typename... Types>
int bind_all(const Types&... types)
{
return bind_internal<Types...>(std::index_sequence_for<Types...>(), types...);
}
private:
template <typename... Types, size_t... Indices>
int bind_internal(std::index_sequence<Indices...>, const Types&... types)
{
return ((bind<Types>(types, Indices + 1) != SQLITE_OK) | ...);
}
sqlite3_stmt* statement;
};
class statement_t
{
public:
explicit statement_t(sqlite3* db, const std::string& stmt);
statement_t(const statement_t& copy) = delete;
statement_t(statement_t& move) noexcept: statement{std::exchange(move.statement, nullptr)}, db{move.db}
{}
statement_t& operator=(const statement_t&) = delete;
statement_t& operator=(statement_t&& move) noexcept
{
statement = std::exchange(move.statement, statement);
db = std::exchange(move.db, db);
return *this;
}
binder_t bind() const // NOLINT
{
sqlite3_reset(statement);
return binder_t{statement};
}
[[jetbrains::has_side_effects]] bool execute() const; // NOLINT
[[nodiscard]] column_t fetch() const
{
return column_t{statement};
}
~statement_t();
private:
sqlite3_stmt* statement = nullptr;
sqlite3* db;
};
class table_builder_t;
class table_column_builder_t
{
public:
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()
{
primary_keys.push_back(name);
return *this;
}
table_column_builder_t& unique()
{
attributes.emplace_back("UNIQUE");
return *this;
}
table_column_builder_t& with_default(const std::string& value)
{
if (type == "TEXT")
attributes.push_back("DEFAULT \"" + value + '"');
else
attributes.push_back("DEFAULT " + value);
return *this;
}
table_column_builder_t& not_null()
{
attributes.emplace_back("NOT NULL");
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)
{
columns.push_back(name + ' ' + type + ' ' + join());
created = true;
}
return parent;
}
~table_column_builder_t()
{
finish();
}
private:
[[nodiscard]] std::string join() const
{
std::string out;
for (const auto& str : attributes)
{
out += str;
out += ' ';
}
return out;
}
bool created = false;
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;
};
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_column(const std::string& 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{};
};
class statement_builder_t
{
public:
explicit statement_builder_t(sqlite3* db): db{db}
{}
table_builder_t create_table(const std::string& name) const
{
return table_builder_t{db, name};
}
private:
sqlite3* db;
};
class database_t
{
public:
explicit database_t(const std::string& file);
database_t(const database_t& copy) = delete;
database_t(database_t& move) noexcept: db{std::exchange(move.db, nullptr)}
{}
database_t& operator=(const database_t&) = delete;
database_t& operator=(database_t&& move) noexcept
{
db = std::exchange(move.db, db);
return *this;
}
[[nodiscard]] statement_t prepare(const std::string& stmt) const
{
return statement_t{db, stmt};
}
[[nodiscard]] statement_builder_t builder() const
{
return statement_builder_t{db};
}
[[nodiscard]] auto get_error() const
{
return sqlite3_errmsg(db);
}
~database_t();
private:
sqlite3* db = nullptr;
};
#endif //SQL_H