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)
|
||||
|
||||
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