Import SQLite3 wrapper implementation from old repo
This commit is contained in:
parent
f9c6e9e7cb
commit
405b68b824
5 changed files with 826 additions and 0 deletions
28
CMake/sqlite3.cmake
Normal file
28
CMake/sqlite3.cmake
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
FetchContent_Declare(
|
||||||
|
sqlite3
|
||||||
|
URL https://www.sqlite.org/2025/sqlite-amalgamation-3490100.zip
|
||||||
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_MakeAvailable(
|
||||||
|
sqlite3
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(
|
||||||
|
sqlite3
|
||||||
|
STATIC
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
sqlite3
|
||||||
|
PUBLIC
|
||||||
|
${sqlite3_SOURCE_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(
|
||||||
|
sqlite3
|
||||||
|
PRIVATE
|
||||||
|
${sqlite3_SOURCE_DIR}/sqlite3.c
|
||||||
|
)
|
742
Garbage/Include/Garbage/SQLite.hpp
Normal file
742
Garbage/Include/Garbage/SQLite.hpp
Normal file
|
@ -0,0 +1,742 @@
|
||||||
|
#ifndef GARBAGE_SQLITE_HPP
|
||||||
|
#define GARBAGE_SQLITE_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
namespace Garbage::SQLite
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// TRAIT SUPPORT
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct MemberTraits;
|
||||||
|
|
||||||
|
template <typename T, typename U>
|
||||||
|
struct MemberTraits<T U::*>
|
||||||
|
{
|
||||||
|
using Member = T;
|
||||||
|
using Container = U;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Implementation
|
||||||
|
{
|
||||||
|
template <typename... Ts>
|
||||||
|
struct First;
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
struct First<T, Ts...>
|
||||||
|
{
|
||||||
|
using Type = T;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Ts>
|
||||||
|
using First = Implementation::First<Ts...>::Type;
|
||||||
|
|
||||||
|
namespace Implementation
|
||||||
|
{
|
||||||
|
template <template <typename> typename Template, typename T>
|
||||||
|
struct IsSpecialization
|
||||||
|
{
|
||||||
|
static constexpr bool Value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <template <typename> typename Template, typename... Args>
|
||||||
|
struct IsSpecialization<Template, Template<Args...>>
|
||||||
|
{
|
||||||
|
static constexpr bool Value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <template <auto> typename Template, typename T>
|
||||||
|
struct IsValueSpecialization
|
||||||
|
{
|
||||||
|
static constexpr bool Value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <template <auto> typename Template, auto... Args>
|
||||||
|
struct IsValueSpecialization<Template, Template<Args...>>
|
||||||
|
{
|
||||||
|
static constexpr bool Value = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <template <typename> typename Template, typename T>
|
||||||
|
concept IsSpecialization = Implementation::IsSpecialization<Template, T>::Value;
|
||||||
|
|
||||||
|
template <template <auto> typename Template, typename T>
|
||||||
|
concept IsValueSpecialization = Implementation::IsValueSpecialization<Template, T>::Value;
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
concept IsSame = std::is_same_v<T1, T2>;
|
||||||
|
|
||||||
|
template <typename T, typename... Ts>
|
||||||
|
concept IsAnyOf = (IsSame<T, Ts> || ...);
|
||||||
|
|
||||||
|
template <template <typename> typename Wrapper, typename T, typename... Ts>
|
||||||
|
concept IsAnyInstanceOf = IsAnyOf<T, Wrapper<Ts>...>;
|
||||||
|
|
||||||
|
namespace Implementation
|
||||||
|
{
|
||||||
|
template <typename Arg, typename... Ts>
|
||||||
|
struct FirstWithArg
|
||||||
|
{
|
||||||
|
using Type = void;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Arg, template <typename> typename T, typename... Ts, typename... Args>
|
||||||
|
struct FirstWithArg<Arg, T<Args...>, Ts...>
|
||||||
|
{
|
||||||
|
using Type = std::conditional_t<IsSame<Arg, typename First<Args...>::Type>,
|
||||||
|
T<Args...>, typename FirstWithArg<Arg, Ts...>::Type>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Arg, typename... Ts>
|
||||||
|
using FirstWithArg = Implementation::FirstWithArg<Arg, Ts...>::Type;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// UTILITY FUNCTIONS
|
||||||
|
|
||||||
|
template <typename DestinationType, typename... TupleTypes>
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
auto ToArray(std::tuple<TupleTypes...>&& tuple)
|
||||||
|
{
|
||||||
|
constexpr auto makeArray = [] (auto&&... args) {
|
||||||
|
return std::array{ std::forward(args)... };
|
||||||
|
};
|
||||||
|
return std::apply(makeArray, tuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, std::size_t N>
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
auto ToArray(auto (&&array)[N])
|
||||||
|
{
|
||||||
|
std::array<T, N> result;
|
||||||
|
std::move(std::begin(array), std::end(array), result.begin());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note(3011): The mutating version is easily addable if need be.
|
||||||
|
template <typename... Ts>
|
||||||
|
constexpr
|
||||||
|
void ForEach(auto&& multifunc, const std::tuple<Ts...>& tuple)
|
||||||
|
{
|
||||||
|
auto conditionalInvoke = [] (auto& multifunc, const auto& item) {
|
||||||
|
if constexpr (std::invocable<decltype(multifunc), decltype(item)>) {
|
||||||
|
multifunc(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::apply([&multifunc, &conditionalInvoke] (const auto&... args) {
|
||||||
|
(conditionalInvoke(multifunc, args), ...);
|
||||||
|
}, tuple);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// ERRORS
|
||||||
|
|
||||||
|
class DatabaseError : public std::runtime_error
|
||||||
|
{
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// BASIC TYPES
|
||||||
|
|
||||||
|
using Integer = std::int64_t;
|
||||||
|
using String = std::string;
|
||||||
|
using Real = double;
|
||||||
|
using Blob = std::vector<std::uint8_t>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using Optional = std::optional<T>;
|
||||||
|
|
||||||
|
namespace Implementation
|
||||||
|
{
|
||||||
|
template <typename T>
|
||||||
|
struct TypeString;
|
||||||
|
|
||||||
|
template <> struct TypeString<Integer> { static constexpr std::string_view Value = "INTEGER"; };
|
||||||
|
template <> struct TypeString<String > { static constexpr std::string_view Value = "TEXT" ; };
|
||||||
|
template <> struct TypeString<Real > { static constexpr std::string_view Value = "REAL" ; };
|
||||||
|
template <> struct TypeString<Blob > { static constexpr std::string_view Value = "BLOB" ; };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct TypeString<Optional<T>>
|
||||||
|
{
|
||||||
|
static constexpr std::string_view Value = TypeString<T>::Value;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static constexpr std::string_view TypeString = Implementation::TypeString<T>::Value;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept SupportedType = IsAnyOf<T, Integer, String, Real, Blob>
|
||||||
|
|| IsAnyInstanceOf<Optional, T, Integer, String, Real, Blob>;
|
||||||
|
|
||||||
|
// TODO: These are only for development, remove once prepared statements are
|
||||||
|
// implemented.
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
std::string ToString(Integer i)
|
||||||
|
{
|
||||||
|
return std::to_string(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
std::string ToString(const String& s)
|
||||||
|
{
|
||||||
|
return "'" + s + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// DATABASE MODEL
|
||||||
|
|
||||||
|
enum class ColumnFlags : std::uint8_t
|
||||||
|
{
|
||||||
|
Nothing = 0b0000'0000,
|
||||||
|
PrimaryKey = 0b0000'0001,
|
||||||
|
Unique = 0b0000'0010,
|
||||||
|
|
||||||
|
LowerBits = 0b0000'1111,
|
||||||
|
UpperBits = 0b1111'0000,
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
ColumnFlags operator| (ColumnFlags a, ColumnFlags b)
|
||||||
|
{
|
||||||
|
using Type = std::underlying_type_t<ColumnFlags>;
|
||||||
|
return ColumnFlags(static_cast<Type>(a) | static_cast<Type>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
ColumnFlags operator& (ColumnFlags a, ColumnFlags b)
|
||||||
|
{
|
||||||
|
using Type = std::underlying_type_t<ColumnFlags>;
|
||||||
|
return ColumnFlags(static_cast<Type>(a) & static_cast<Type>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <ColumnFlags Flag>
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
bool IsSet(ColumnFlags a)
|
||||||
|
{
|
||||||
|
return (a & Flag) != ColumnFlags::Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
requires std::is_member_object_pointer_v<T>
|
||||||
|
&& SupportedType<typename MemberTraits<T>::Member>
|
||||||
|
class Column
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using PtrType = T;
|
||||||
|
using DataType = MemberTraits<T>::Member;
|
||||||
|
using TableType = MemberTraits<T>::Container;
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
Column(DataType TableType::*ptr, std::string_view name, ColumnFlags flags = ColumnFlags::Nothing)
|
||||||
|
: mPtr(ptr), mFlags(flags), mName(name)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::string_view Name() const
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view Type() const
|
||||||
|
{
|
||||||
|
return TypeString<DataType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnFlags Flags() const
|
||||||
|
{
|
||||||
|
return mFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOptional() const
|
||||||
|
{
|
||||||
|
// Note: it doesn't work with the Optional alias
|
||||||
|
return IsSpecialization<std::optional, DataType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Declaration() const
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result += mName + " " + std::string(Type());
|
||||||
|
if (!IsOptional()) {
|
||||||
|
result += " NOT NULL";
|
||||||
|
}
|
||||||
|
switch (mFlags & ColumnFlags::LowerBits) {
|
||||||
|
break; case ColumnFlags::Nothing:
|
||||||
|
// noop
|
||||||
|
break; case ColumnFlags::PrimaryKey:
|
||||||
|
result += " PRIMARY KEY";
|
||||||
|
break; case ColumnFlags::Unique:
|
||||||
|
result += " UNIQUE";
|
||||||
|
break; default:
|
||||||
|
throw DatabaseError("Invalid column flag for: " + mName);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataType TableType::*Ptr() const
|
||||||
|
{
|
||||||
|
return mPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DataType& Get(const TableType& item) const
|
||||||
|
{
|
||||||
|
return item.*mPtr;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
DataType TableType::*mPtr;
|
||||||
|
ColumnFlags mFlags;
|
||||||
|
std::string mName;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Column(T, auto) -> Column<T>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
Column(T, auto, ColumnFlags) -> Column<T>;
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
class ForeignKey
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
template <typename StringLike>
|
||||||
|
requires std::is_convertible_v<StringLike, std::string>
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
ForeignKey(StringLike (&&from)[N], std::string_view table, StringLike (&&to)[N])
|
||||||
|
: mDestinationTable(table)
|
||||||
|
, mFromColumns(ToArray<std::string>(std::move(from)))
|
||||||
|
, mToColumns(ToArray<std::string>(std::move(to)))
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::string Declaration() const
|
||||||
|
{
|
||||||
|
std::string fromColumns;
|
||||||
|
std::string toColumns;
|
||||||
|
for (std::size_t i = 0; i < mFromColumns.size(); ++i) {
|
||||||
|
fromColumns += mFromColumns[i];
|
||||||
|
toColumns += mToColumns[i];
|
||||||
|
if (i < N - 1) {
|
||||||
|
fromColumns += ", ";
|
||||||
|
toColumns += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "FOREIGN KEY(" + fromColumns + ") REFERENCES " + mDestinationTable + "(" + toColumns + ")";
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string mDestinationTable;
|
||||||
|
std::array<std::string, N> mFromColumns;
|
||||||
|
std::array<std::string, N> mToColumns;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
concept MappableType = requires {
|
||||||
|
requires std::is_aggregate_v<T>;
|
||||||
|
requires std::is_default_constructible_v<T>;
|
||||||
|
requires !std::is_array_v<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename TableType>
|
||||||
|
concept TableEntity = IsValueSpecialization<ForeignKey, T>
|
||||||
|
|| (IsSpecialization<Column, T> && IsSame<TableType, typename T::TableType>);
|
||||||
|
|
||||||
|
template <MappableType T, TableEntity<T>... Entities>
|
||||||
|
requires (sizeof...(Entities) > 0)
|
||||||
|
class Table
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
Table(std::string_view name, Entities&&... entities)
|
||||||
|
: mName(name), mEntities(std::move(entities)...)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::string_view Name() const
|
||||||
|
{
|
||||||
|
return mName;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ColumnPtr>
|
||||||
|
std::string_view Name(ColumnPtr ptr) const
|
||||||
|
{
|
||||||
|
std::string_view name;
|
||||||
|
ForEach([&name, &ptr] (const Column<ColumnPtr>& column) {
|
||||||
|
if (ptr == column.Ptr()) {
|
||||||
|
name = column.Name();
|
||||||
|
}
|
||||||
|
}, mEntities);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ColumnPtr>
|
||||||
|
std::string Identifier(ColumnPtr ptr) const
|
||||||
|
{
|
||||||
|
return "$" + mName + "::" + std::string(Name(ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Create() const
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result += "CREATE TABLE IF NOT EXISTS " + mName + " (\n";
|
||||||
|
ForEach([&result] <typename ColumnType> (const Column<ColumnType>& column) {
|
||||||
|
result += " ";
|
||||||
|
result += column.Declaration();
|
||||||
|
result += ",\n";
|
||||||
|
}, mEntities);
|
||||||
|
ForEach([&result] <std::size_t Size> (const ForeignKey<Size>& key) {
|
||||||
|
result += " ";
|
||||||
|
result += key.Declaration();
|
||||||
|
result += ",\n";
|
||||||
|
}, mEntities);
|
||||||
|
result.erase(result.size() - 2, 1);
|
||||||
|
result.back() = '\n';
|
||||||
|
result += ");\n";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Insert() const
|
||||||
|
{
|
||||||
|
std::string columnNames;
|
||||||
|
std::string valueNames;
|
||||||
|
ForEach([this, &columnNames, &valueNames] <typename ColumnType> (const Column<ColumnType>& column) {
|
||||||
|
if (IsSet<ColumnFlags::PrimaryKey>(column.Flags())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
columnNames += column.Name();
|
||||||
|
columnNames += ", ";
|
||||||
|
valueNames += Identifier(column.Ptr());
|
||||||
|
valueNames += ", ";
|
||||||
|
}, mEntities);
|
||||||
|
std::string result;
|
||||||
|
result += "INSERT INTO " + mName + "(";
|
||||||
|
result += columnNames.erase(columnNames.size() - 2);
|
||||||
|
result += ") VALUES (";
|
||||||
|
result += valueNames.erase(valueNames.size() - 2);
|
||||||
|
result += ");\n";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: These are only for development, remove once prepared statements are
|
||||||
|
// implemented.
|
||||||
|
std::string Insert(const T& item) const
|
||||||
|
{
|
||||||
|
std::string columnNames;
|
||||||
|
std::string values;
|
||||||
|
ForEach([&item, &columnNames, &values] <typename ColumnType> (const Column<ColumnType>& column) {
|
||||||
|
if (IsSet<ColumnFlags::PrimaryKey>(column.Flags())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
columnNames += column.Name();
|
||||||
|
columnNames += ", ";
|
||||||
|
values += ToString(column.Get(item));
|
||||||
|
values += ", ";
|
||||||
|
}, mEntities);
|
||||||
|
std::string result;
|
||||||
|
result += "INSERT INTO " + mName + "(";
|
||||||
|
result += columnNames.erase(columnNames.size() - 2);
|
||||||
|
result += ") VALUES (";
|
||||||
|
result += values.erase(values.size() - 2);
|
||||||
|
result += ");\n";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Update() const
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result += "UPDATE " + mName + " SET\n";
|
||||||
|
ForEach([this, &result] <typename ColumnType> (const Column<ColumnType>& column) {
|
||||||
|
if (IsSet<ColumnFlags::PrimaryKey>(column.Flags())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result += " " + std::string(column.Name()) + " = " + Identifier(column.Ptr()) + ",\n";
|
||||||
|
}, mEntities);
|
||||||
|
result.erase(result.size() - 2, 1);
|
||||||
|
ForEach([this, &result] <typename ColumnType> (const Column<ColumnType>& column) {
|
||||||
|
if (IsSet<ColumnFlags::PrimaryKey>(column.Flags())) {
|
||||||
|
result += "WHERE " + std::string(column.Name()) + " = " + Identifier(column.Ptr()) + ";\n";
|
||||||
|
}
|
||||||
|
}, mEntities);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: These are only for development, remove once prepared statements are
|
||||||
|
// implemented.
|
||||||
|
std::string Update(const T& item) const
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result += "UPDATE " + mName + " SET\n";
|
||||||
|
ForEach([this, &result, &item] <typename ColumnType> (const Column<ColumnType>& column) {
|
||||||
|
if (IsSet<ColumnFlags::PrimaryKey>(column.Flags())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result += " " + std::string(column.Name()) + " = " + ToString(column.Get(item)) + ",\n";
|
||||||
|
}, mEntities);
|
||||||
|
result.erase(result.size() - 2, 1);
|
||||||
|
ForEach([this, &result, &item] <typename ColumnType> (const Column<ColumnType>& column) {
|
||||||
|
if (IsSet<ColumnFlags::PrimaryKey>(column.Flags())) {
|
||||||
|
result += "WHERE " + std::string(column.Name()) + " = " + ToString(column.Get(item)) + ";\n";
|
||||||
|
}
|
||||||
|
}, mEntities);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string mName;
|
||||||
|
std::tuple<Entities...> mEntities;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note(3011): This breaks when the first specified entity is not a column.
|
||||||
|
template <typename... Entities>
|
||||||
|
Table(auto, Entities&&...) -> Table<typename MemberTraits<typename First<Entities...>::PtrType>::Container, Entities...>;
|
||||||
|
|
||||||
|
template <typename... Tables>
|
||||||
|
requires (IsSpecialization<Table, Tables> && ...)
|
||||||
|
class Database
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] constexpr
|
||||||
|
Database(std::string_view name, Tables&&... tables)
|
||||||
|
: mName(name), mTables(std::move(tables)...)
|
||||||
|
{}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
std::string_view Name() const
|
||||||
|
{
|
||||||
|
using Type = FirstWithArg<TableType, Tables...>;
|
||||||
|
return std::get<Type>(mTables).Name();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ColumnPtr>
|
||||||
|
requires std::is_member_object_pointer_v<ColumnPtr>
|
||||||
|
std::string_view Name(ColumnPtr ptr) const
|
||||||
|
{
|
||||||
|
using TableType = MemberTraits<ColumnPtr>::Container;
|
||||||
|
using Type = FirstWithArg<TableType, Tables...>;
|
||||||
|
return std::get<Type>(mTables).Name(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ColumnPtr>
|
||||||
|
requires std::is_member_object_pointer_v<ColumnPtr>
|
||||||
|
std::string Identifier(ColumnPtr ptr) const
|
||||||
|
{
|
||||||
|
using TableType = MemberTraits<ColumnPtr>::Container;
|
||||||
|
using Type = FirstWithArg<TableType, Tables...>;
|
||||||
|
return std::get<Type>(mTables).Identifier(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Create() const
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result += "PRAGMA foreign_keys = ON;\n\n";
|
||||||
|
std::apply([&result](auto&... args) {
|
||||||
|
((result += args.Create(), result += "\n"), ...);
|
||||||
|
}, mTables);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
std::string Insert() const
|
||||||
|
{
|
||||||
|
using Type = FirstWithArg<TableType, Tables...>;
|
||||||
|
return std::get<Type>(mTables).Insert();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
std::string Insert(const TableType& item) const
|
||||||
|
{
|
||||||
|
using Type = FirstWithArg<TableType, Tables...>;
|
||||||
|
return std::get<Type>(mTables).Insert(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
std::string Update() const
|
||||||
|
{
|
||||||
|
using Type = FirstWithArg<TableType, Tables...>;
|
||||||
|
return std::get<Type>(mTables).Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
std::string Update(const TableType& item) const
|
||||||
|
{
|
||||||
|
using Type = FirstWithArg<TableType, Tables...>;
|
||||||
|
return std::get<Type>(mTables).Update(item);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::string mName;
|
||||||
|
std::tuple<Tables...> mTables;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... Tables>
|
||||||
|
Database(auto, Tables...) -> Database<Tables...>;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// DATABASE CONNECTION
|
||||||
|
|
||||||
|
enum class Operator : std::uint8_t
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Equal = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename DatabaseType>
|
||||||
|
requires IsSpecialization<Database, DatabaseType>
|
||||||
|
class Connection;
|
||||||
|
|
||||||
|
template <typename DatabaseType, typename TableType>
|
||||||
|
class Select
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
friend class Connection<DatabaseType>;
|
||||||
|
|
||||||
|
Select(Connection<DatabaseType>& connection)
|
||||||
|
: mConnection(connection)
|
||||||
|
{}
|
||||||
|
public:
|
||||||
|
// Note: Immovable object, please don't use unstoppable force.
|
||||||
|
Select(const Select&) = delete;
|
||||||
|
Select& operator=(const Select&) = delete;
|
||||||
|
Select(Select&&) = delete;
|
||||||
|
Select& operator=(Select&&) = delete;
|
||||||
|
|
||||||
|
template <typename ColumnPtr>
|
||||||
|
requires std::is_member_object_pointer_v<ColumnPtr>
|
||||||
|
Select& Where(ColumnPtr column)
|
||||||
|
{
|
||||||
|
// TODO: Append the condition.
|
||||||
|
return &this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Finalizer member function - returns the result.
|
||||||
|
std::optional<TableType> First()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Finalizer member function - returns the result.
|
||||||
|
std::vector<TableType> All()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Connection<TableType>& mConnection;
|
||||||
|
std::string mStatement;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename DatabaseType>
|
||||||
|
requires IsSpecialization<Database, DatabaseType>
|
||||||
|
class Connection
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Connection(const DatabaseType& database, std::string path)
|
||||||
|
: mDatabase(&database), mPath(std::move(path)), mConnection(nullptr)
|
||||||
|
{
|
||||||
|
int errc = sqlite3_open(mPath.c_str(), &mConnection);
|
||||||
|
if (errc != SQLITE_OK) {
|
||||||
|
// TODO: Add more information to the error.
|
||||||
|
throw DatabaseError("Could not open a SQLite connection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection(const Connection&) = delete;
|
||||||
|
Connection& operator=(const Connection&) = delete;
|
||||||
|
|
||||||
|
Connection(Connection&& other) noexcept
|
||||||
|
: mDatabase(other.mDatabase)
|
||||||
|
, mPath(std::move(other.mPath))
|
||||||
|
, mConnection(other.mConnection)
|
||||||
|
{
|
||||||
|
other.mConnection = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connection& operator= (Connection&& other)
|
||||||
|
{
|
||||||
|
if (&other == this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDatabase = other.mDatabase;
|
||||||
|
mPath = std::move(other.mPath);
|
||||||
|
mConnection = other.mConnection;
|
||||||
|
|
||||||
|
other.mDatabase = nullptr;
|
||||||
|
other.mConnection = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
~Connection()
|
||||||
|
{
|
||||||
|
// TODO: The current implementation will loop here forever in case
|
||||||
|
// anything isn't finalized - make sure to force a cleanup before
|
||||||
|
// that happens.
|
||||||
|
int errc = sqlite3_close(mConnection);
|
||||||
|
while (errc == SQLITE_BUSY) {
|
||||||
|
errc = sqlite3_close(mConnection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Create()
|
||||||
|
{
|
||||||
|
std::string script = mDatabase->Create();
|
||||||
|
char *errmsg;
|
||||||
|
auto errc = sqlite3_exec(mConnection, script.c_str(), nullptr, nullptr, &errmsg);
|
||||||
|
if (errc != SQLITE_OK) {
|
||||||
|
// TODO: Report the error?
|
||||||
|
sqlite3_free(reinterpret_cast<void *>(errc));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
bool Insert(TableType& item)
|
||||||
|
{
|
||||||
|
std::string statement = mDatabase->Insert(item);
|
||||||
|
char *errmsg;
|
||||||
|
auto errc = sqlite3_exec(mConnection, statement.c_str(), nullptr, nullptr, &errmsg);
|
||||||
|
if (errc != SQLITE_OK) {
|
||||||
|
// TODO: Report the error?
|
||||||
|
sqlite3_free(reinterpret_cast<void *>(errc));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
bool Update(TableType& item)
|
||||||
|
{
|
||||||
|
std::string statement = mDatabase->Insert(item);
|
||||||
|
char *errmsg;
|
||||||
|
auto errc = sqlite3_exec(mConnection, statement.c_str(), nullptr, nullptr, &errmsg);
|
||||||
|
if (errc != SQLITE_OK) {
|
||||||
|
// TODO: Report the error?
|
||||||
|
sqlite3_free(reinterpret_cast<void *>(errc));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TableType>
|
||||||
|
Select<DatabaseType, TableType> Select()
|
||||||
|
{
|
||||||
|
return Select<DatabaseType, TableType>(*this);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
const DatabaseType *mDatabase;
|
||||||
|
std::string mPath;
|
||||||
|
sqlite3 *mConnection;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // GARBAGE_SQLITE_HPP
|
|
@ -1,3 +1,4 @@
|
||||||
include(${PROJECT_SOURCE_DIR}/CMake/Catch2.cmake)
|
include(${PROJECT_SOURCE_DIR}/CMake/Catch2.cmake)
|
||||||
|
|
||||||
add_subdirectory(Base64)
|
add_subdirectory(Base64)
|
||||||
|
add_subdirectory(SQLite)
|
||||||
|
|
20
Tests/SQLite/CMakeLists.txt
Normal file
20
Tests/SQLite/CMakeLists.txt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
include(${PROJECT_SOURCE_DIR}/CMake/sqlite3.cmake)
|
||||||
|
|
||||||
|
add_executable(TestSQLite)
|
||||||
|
|
||||||
|
target_compile_features(TestSQLite
|
||||||
|
PRIVATE
|
||||||
|
cxx_std_20
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(TestSQLite
|
||||||
|
PRIVATE
|
||||||
|
Garbage
|
||||||
|
sqlite3
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(TestSQLite
|
||||||
|
PRIVATE
|
||||||
|
Main.cpp
|
||||||
|
)
|
35
Tests/SQLite/Main.cpp
Normal file
35
Tests/SQLite/Main.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include <Garbage/SQLite.hpp>
|
||||||
|
|
||||||
|
using namespace Garbage::SQLite;
|
||||||
|
|
||||||
|
struct User
|
||||||
|
{
|
||||||
|
std::int64_t Id;
|
||||||
|
std::string Username;
|
||||||
|
std::string Email;
|
||||||
|
std::string PasswordHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
Database db(
|
||||||
|
"UserDb",
|
||||||
|
Table(
|
||||||
|
"Users",
|
||||||
|
Column(&User::Id, "Id", ColumnFlags::PrimaryKey),
|
||||||
|
Column(&User::Username, "Username", ColumnFlags::Unique),
|
||||||
|
Column(&User::Email, "Email", ColumnFlags::Unique),
|
||||||
|
Column(&User::PasswordHash, "PasswordHash")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
User u {
|
||||||
|
.Id = 25,
|
||||||
|
.Username = "noob",
|
||||||
|
.Email = "noob@noob.io",
|
||||||
|
.PasswordHash = "ASDASDASDASDASDA",
|
||||||
|
};
|
||||||
|
|
||||||
|
Connection c(db, "Test.db");
|
||||||
|
c.Create();
|
||||||
|
c.Insert(u);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue