diff --git a/Bigfoot/Sources/Engine/BigFile/BigFile.cpp b/Bigfoot/Sources/Engine/BigFile/BigFile.cpp new file mode 100644 index 0000000..3805a38 --- /dev/null +++ b/Bigfoot/Sources/Engine/BigFile/BigFile.cpp @@ -0,0 +1,275 @@ +/********************************************************************* + * \file BigFile.cpp + * + * \author Romain BOULLARD + * \date October 2025 + *********************************************************************/ +#include + +#include + +namespace Bigfoot +{ +BigFile::BigFile(const File& p_file) +{ + [[maybe_unused]] + const int result = sqlite3_open_v2(p_file.Absolute().Path().data(), &m_db, SQLITE_OPEN_READWRITE, nullptr); + CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to open BigFile DB: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::BeginTransaction() +{ + [[maybe_unused]] + const int result = sqlite3_exec(m_db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to begin transaction: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::CommitTransaction() +{ + [[maybe_unused]] + const int result = sqlite3_exec(m_db, "COMMIT TRANSACTION;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to commit: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::RollbackTransaction() +{ + [[maybe_unused]] + const int result = sqlite3_exec(m_db, "ROLLBACK TRANSACTION;", nullptr, nullptr, nullptr); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to rollback: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +BigFile::~BigFile() +{ + [[maybe_unused]] + const int result = sqlite3_close_v2(m_db); + CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to close BigFile DB: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +BigFile::Request::Request(const BigFile& p_bigFile, const eastl::string_view p_request): + m_db(p_bigFile.m_db) +{ + [[maybe_unused]] + const int result = sqlite3_prepare_v2(m_db, + p_request.data(), + static_cast(p_request.size()), + &m_statement, + nullptr); + CRITICAL_ASSERT(EngineAssertHandler, + result == SQLITE_OK, + "Failed to create statement from BigFile DB: {}", + sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const std::int32_t p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_int(m_statement, p_index, p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const std::uint32_t p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_int(m_statement, p_index, p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const std::int64_t p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_int64(m_statement, p_index, p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const float p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_double(m_statement, p_index, p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const double p_value) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_double(m_statement, p_index, p_value); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, const eastl::string_view p_value, const CopyValue p_copy) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_text(m_statement, + p_index, + p_value.data(), + static_cast(p_value.size()), + p_copy ? SQLITE_TRANSIENT : SQLITE_STATIC); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +void BigFile::Request::Bind(const std::uint32_t p_index, + const eastl::span p_value, + const CopyValue p_copy) +{ + ASSERT(EngineAssertHandler, + (p_index >= 1) && (p_index <= static_cast(sqlite3_bind_parameter_count(m_statement))), + "Invalid index for statement"); + + [[maybe_unused]] + const int result = sqlite3_bind_blob(m_statement, + p_index, + p_value.data(), + static_cast(p_value.size()), + p_copy ? SQLITE_TRANSIENT : SQLITE_STATIC); + ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to bind value for statement: {}", sqlite3_errmsg(m_db)); +} + +/****************************************************************************************/ + +bool BigFile::Request::Step() +{ + const int result = sqlite3_step(m_statement); + ASSERT(EngineAssertHandler, + (result == SQLITE_DONE) || (result == SQLITE_ROW), + "Failed to step through the statement: {}", + sqlite3_errmsg(m_db)); + + return result == SQLITE_ROW; +} + +/****************************************************************************************/ + +std::uint32_t BigFile::Request::Execute() +{ + [[maybe_unused]] + const int result = sqlite3_step(m_statement); + ASSERT(EngineAssertHandler, (result == SQLITE_DONE), "Failed to execute the statement: {}", sqlite3_errmsg(m_db)); + + return sqlite3_changes(m_db); +} + +/****************************************************************************************/ + +BigFile::Request::Column::Column(const Request& p_request, const std::uint32_t p_index): + m_statement(p_request.m_statement), + m_index(p_index) +{ +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::int32_t() const +{ + return sqlite3_column_int(m_statement, m_index); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::uint32_t() const +{ + return sqlite3_column_int(m_statement, m_index); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator std::int64_t() const +{ + return sqlite3_column_int64(m_statement, m_index); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator float() const +{ + return static_cast(sqlite3_column_double(m_statement, m_index)); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator double() const +{ + return sqlite3_column_double(m_statement, m_index); +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator eastl::string_view() const +{ + return eastl::string_view {reinterpret_cast(sqlite3_column_text(m_statement, m_index)), + static_cast(sqlite3_column_bytes(m_statement, m_index))}; +} + +/****************************************************************************************/ + +BigFile::Request::Column::operator eastl::span() const +{ + return eastl::span {static_cast(sqlite3_column_blob(m_statement, m_index)), + static_cast(sqlite3_column_bytes(m_statement, m_index))}; +} + +/****************************************************************************************/ + +BigFile::Request::Column BigFile::Request::Get(const std::uint32_t p_index) const +{ + ASSERT(EngineAssertHandler, + p_index < static_cast(sqlite3_column_count(m_statement)), + "Invalid index for column!"); + return {*this, p_index}; +} + +/****************************************************************************************/ + +BigFile::Request::~Request() +{ + [[maybe_unused]] + const int result = sqlite3_finalize(m_statement); + CRITICAL_ASSERT(EngineAssertHandler, result == SQLITE_OK, "Failed to finalize statement: {}", sqlite3_errmsg(m_db)); +} +} // namespace Bigfoot diff --git a/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp new file mode 100644 index 0000000..f3cd515 --- /dev/null +++ b/Bigfoot/Sources/Engine/Include/Engine/BigFile/BigFile.hpp @@ -0,0 +1,262 @@ +/********************************************************************* + * \file BigFile.hpp + * + * \author Romain BOULLARD + * \date October 2025 + *********************************************************************/ +#ifndef BIGFOOT_ENGINE_BIGFILE_HPP +#define BIGFOOT_ENGINE_BIGFILE_HPP +#include + +#include + +#include + +#include + +namespace Bigfoot +{ +class BigFile +{ + public: + /** + * Constructor + * + * \param p_file The file targetting the BigFile DB + */ + BigFile(const File& p_file); + + BigFile(const BigFile& p_bigFile) = default; + BigFile(BigFile&& p_bigFile) = default; + + /** + * Begin a transaction + * + */ + void BeginTransaction(); + + /** + * Commit a transaction + * + */ + void CommitTransaction(); + + /** + * Rollback a transaction + * + */ + void RollbackTransaction(); + + ~BigFile(); + + BigFile& operator=(const BigFile& p_bigFile) = default; + BigFile& operator=(BigFile&& p_bigFile) = default; + + class Request + { + public: + /* + * Constructor + * + * \param p_bigFile The bigfile + * \param p_request The SQL request + */ + Request(const BigFile& p_bigFile, const eastl::string_view p_request); + + Request(const Request& p_request) = default; + Request(Request&& p_request) = default; + + using CopyValue = TaggedType; + + /* + * Bind a int32 value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const std::int32_t p_value); + + /* + * Bind a uint32 value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const std::uint32_t p_value); + + /* + * Bind a int64 value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const std::int64_t p_value); + + /* + * Bind a float value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const float p_value); + + /* + * Bind a double value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + */ + void Bind(const std::uint32_t p_index, const double p_value); + + /* + * Bind a string value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + * \param p_copy Should the memory be copied so that caller does not have to retain it + */ + void Bind(const std::uint32_t p_index, + const eastl::string_view p_value, + const CopyValue p_copy = CopyValue {false}); + + /* + * Bind a blob value to the Request at index + * + * \param p_index The index to bind to + * \param p_value The value + * \param p_copy Should the memory be copied so that caller does not have to retain it + */ + void Bind(const std::uint32_t p_index, + const eastl::span p_value, + const CopyValue p_copy = CopyValue {false}); + + /* + * Step through the request + * + * \return True if the request can continue (get next row), false otherwise + */ + [[nodiscard]] + bool Step(); + + /* + * Execute the request once + * + * The number of modified rows + */ + [[maybe_unused]] + std::uint32_t Execute(); + + class Column + { + public: + /* + * Constructor + * + * \param p_request The request to get the column of + * \param p_index Column index + */ + Column(const Request& p_request, const std::uint32_t p_index); + + Column(const Column& p_column) = default; + Column(Column&& p_column) = default; + + ~Column() = default; + + Column& operator=(const Column& p_column) = default; + Column& operator=(Column&& p_column) = default; + + /* + * Get value as a int32 + * + * \return The value + */ + operator std::int32_t() const; + + /* + * Get value as a uint32 + * + * \return The value + */ + operator std::uint32_t() const; + + /* + * Get value as a int64 + * + * \return The value + */ + operator std::int64_t() const; + + /* + * Get value as a float + * + * \return The value + */ + operator float() const; + + /* + * Get value as a double + * + * \return The value + */ + operator double() const; + + /* + * Get value as a string + * + * \return The value + */ + operator eastl::string_view() const; + + /* + * Get value as a blob + * + * \return The value + */ + operator eastl::span() const; + + private: + /* + * The statement + */ + sqlite3_stmt* m_statement; + + /* + * The column index + */ + std::uint32_t m_index; + }; + + /* + * Get a column + * + * \param p_index Column index + */ + [[nodiscard]] + Column Get(const std::uint32_t p_index) const; + + ~Request(); + + Request& operator=(const Request& p_request) = default; + Request& operator=(Request&& p_request) = default; + + private: + /* + * The database + */ + sqlite3* m_db; + + /* + * The statement + */ + sqlite3_stmt* m_statement; + }; + + private: + /** + * The BigFile DB + */ + sqlite3* m_db; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Sources/Engine/touch.cpp b/Bigfoot/Sources/Engine/touch.cpp deleted file mode 100644 index cc4afd9..0000000 --- a/Bigfoot/Sources/Engine/touch.cpp +++ /dev/null @@ -1 +0,0 @@ -// to delete when an actual source is in Engine diff --git a/Bigfoot/Tests/Engine/BigFile/BigFile.cpp b/Bigfoot/Tests/Engine/BigFile/BigFile.cpp new file mode 100644 index 0000000..1db95e2 --- /dev/null +++ b/Bigfoot/Tests/Engine/BigFile/BigFile.cpp @@ -0,0 +1,158 @@ +/********************************************************************* + * \file BigFile.cpp + * + * \author Romain BOULLARD + * \date December 2025 + *********************************************************************/ +#include + +#include +#include +#include + +#include +#include + +#include + +#include + +namespace Bigfoot +{ +class BigFileFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + BigFile::Request deleteHeader {m_bigFile, "DELETE FROM AssetHeader"}; + BigFile::Request deleteAsset {m_bigFile, "DELETE FROM AssetHeader"}; + + m_bigFile.BeginTransaction(); + deleteHeader.Execute(); + deleteAsset.Execute(); + m_bigFile.CommitTransaction(); + } + + BIGFOOT_NOT_OPTIMIZED_ONLY(Singleton::Lifetime m_loggerLifetime;) + + BigFile m_bigFile {File {BIGFILE_ENGINETESTS_LOCATION}}; +}; + +/****************************************************************************************/ + +TEST_F(BigFileFixture, Lol) +{ + UUID uuid; + UUID uuid2; + + eastl::array blob {std::byte {1}, std::byte {2}, std::byte {3}, std::byte {4}}; + eastl::array blob2 {std::byte {1}, std::byte {2}, std::byte {3}, std::byte {5}}; + eastl::array blob3 {std::byte {1}, std::byte {2}, std::byte {3}, std::byte {6}}; + + { + BigFile::Request assetHeaderRequest { + m_bigFile, + "INSERT INTO AssetHeader (UUID, Name, TypeID, TypeName) VALUES(?, ?, ?, ?)"}; + assetHeaderRequest.Bind(1, static_cast>(uuid)); + assetHeaderRequest.Bind(2, "Test"); + assetHeaderRequest.Bind(3, 42); + assetHeaderRequest.Bind(4, "TypeTest"); + + BigFile::Request assetRequest {m_bigFile, "INSERT INTO Asset (UUID, Asset) VALUES(?, ?)"}; + assetRequest.Bind(1, static_cast>(uuid)); + assetRequest.Bind(2, blob); + + BigFile::Request assetHeaderRequest2 { + m_bigFile, + "INSERT INTO AssetHeader (UUID, Name, TypeID, TypeName) VALUES(?, ?, ?, ?)"}; + assetHeaderRequest2.Bind(1, static_cast>(uuid2)); + assetHeaderRequest2.Bind(2, "Test2"); + assetHeaderRequest2.Bind(3, 42); + assetHeaderRequest2.Bind(4, "TypeTest"); + + BigFile::Request assetRequest2 {m_bigFile, "INSERT INTO Asset (UUID, Asset) VALUES(?, ?)"}; + assetRequest2.Bind(1, static_cast>(uuid2)); + assetRequest2.Bind(2, blob3); + + m_bigFile.BeginTransaction(); + [[maybe_unused]] + std::uint32_t assetHeaderChangedCount = assetHeaderRequest.Execute(); + [[maybe_unused]] + std::uint32_t assetChangedCount = assetRequest.Execute(); + + [[maybe_unused]] + std::uint32_t assetHeaderChangedCount2 = assetHeaderRequest2.Execute(); + [[maybe_unused]] + std::uint32_t assetChangedCount2 = assetRequest2.Execute(); + + m_bigFile.CommitTransaction(); + } + + { + BigFile::Request updateAsset {m_bigFile, "UPDATE Asset SET Asset = ? WHERE UUID = ?"}; + updateAsset.Bind(1, blob2); + updateAsset.Bind(2, static_cast>(uuid)); + + m_bigFile.BeginTransaction(); + [[maybe_unused]] + std::uint32_t updateAssetChangedCount = updateAsset.Execute(); + m_bigFile.CommitTransaction(); + } + + { + BigFile::Request request { + m_bigFile, + "SELECT Name, TypeID, TypeName, CreateTime, ModificationTime FROM AssetHeader WHERE UUID = ?"}; + request.Bind(1, static_cast>(uuid)); + + [[maybe_unused]] + const bool get = request.Step(); + + [[maybe_unused]] + const eastl::string name {static_cast(request.Get(0))}; + [[maybe_unused]] + const std::uint32_t typeId = request.Get(1); + [[maybe_unused]] + const eastl::string typeName {static_cast(request.Get(2))}; + [[maybe_unused]] + const Time createTime = static_cast(request.Get(3)); + [[maybe_unused]] + const Time modificationTime = static_cast(request.Get(4)); + + { + [[maybe_unused]] + const std::uint32_t year = createTime.Year(); + [[maybe_unused]] + const std::uint32_t month = createTime.Month(); + [[maybe_unused]] + const std::uint32_t day = createTime.Day(); + [[maybe_unused]] + const std::uint32_t hour = createTime.Hour(); + [[maybe_unused]] + const std::uint32_t minute = createTime.Minute(); + [[maybe_unused]] + const std::uint32_t second = createTime.Second(); + [[maybe_unused]] + const std::uint32_t microsecond = createTime.Microsecond(); + } + + { + [[maybe_unused]] + const std::uint32_t year = modificationTime.Year(); + [[maybe_unused]] + const std::uint32_t month = modificationTime.Month(); + [[maybe_unused]] + const std::uint32_t day = modificationTime.Day(); + [[maybe_unused]] + const std::uint32_t hour = modificationTime.Hour(); + [[maybe_unused]] + const std::uint32_t minute = modificationTime.Minute(); + [[maybe_unused]] + const std::uint32_t second = modificationTime.Second(); + [[maybe_unused]] + const std::uint32_t microsecond = modificationTime.Microsecond(); + } + } +} + +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Engine/touch.cpp b/Bigfoot/Tests/Engine/touch.cpp deleted file mode 100644 index 38a47cf..0000000 --- a/Bigfoot/Tests/Engine/touch.cpp +++ /dev/null @@ -1 +0,0 @@ -// to delete when an actual test is in EngineTests