diff options
author | Dominick Allen <djallen@librehumanitas.org> | 2024-09-26 07:46:06 -0500 |
---|---|---|
committer | Dominick Allen <djallen@librehumanitas.org> | 2024-09-26 07:46:06 -0500 |
commit | 63711877057f1f89b4d1774e24fe20907a3af656 (patch) | |
tree | 57c8ed2dcd3463bc782e8be82557e33d6165718a | |
parent | dbb305fa27baada32d29d6f8904bdc02ac494e13 (diff) |
Add SQLite interface.
-rw-r--r-- | CMakeLists.txt | 11 | ||||
-rw-r--r-- | include/fud_sqlite.hpp | 138 | ||||
-rw-r--r-- | include/fud_status.hpp | 3 | ||||
-rw-r--r-- | source/fud_sqlite.cpp | 220 | ||||
-rw-r--r-- | test/CMakeLists.txt | 1 | ||||
-rw-r--r-- | test/test_sqlite.cpp | 49 |
6 files changed, 419 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index d5ebb4e..348cd36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,8 @@ set(CXX_CPPCHECK "project=build/compile_commands.json;enable=information;force") set(CMAKE_EXPORT_COMPILE_COMMANDS true) +find_package(SQLite3 REQUIRED) + add_library(fud SHARED source/libfud.cpp source/fud_assert.cpp @@ -21,12 +23,15 @@ add_library(fud SHARED source/fud_string.cpp source/fud_utf8.cpp source/fud_utf8_iterator.cpp + source/fud_sqlite.cpp ) include(cmake/warnings.cmake) target_compile_options(fud PRIVATE "${FUD_WARNINGS}") -target_include_directories(fud PUBLIC include) +target_include_directories(fud PUBLIC include ${SQLite3_INCLUDE_DIRS}) + +target_link_libraries(fud ${SQLite3_LIBRARIES}) set_target_properties( fud PROPERTIES @@ -36,7 +41,6 @@ set_target_properties( C_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON) - if (FUD_TEST) add_subdirectory(test) # set(CVG_FLAGS -fsanitize=address -fsanitize=undefined --coverage) @@ -79,6 +83,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/FudConfig.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Fud ) set(FUD_HEADERS + "include/libfud.hpp" "include/fud_array.hpp" "include/fud_assert.hpp" "include/fud_c_file.hpp" @@ -90,7 +95,7 @@ set(FUD_HEADERS "include/fud_unique_array.hpp" "include/fud_utf8.hpp" "include/fud_utf8_iterator.hpp" - "include/libfud.hpp" + "include/fud_sqlite" ) set_target_properties(fud PROPERTIES PUBLIC_HEADER "${FUD_HEADERS}") diff --git a/include/fud_sqlite.hpp b/include/fud_sqlite.hpp new file mode 100644 index 0000000..26e9de3 --- /dev/null +++ b/include/fud_sqlite.hpp @@ -0,0 +1,138 @@ +/* + * libfud + * Copyright 2024 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FUD_SQLITE_HPP +#define FUD_SQLITE_HPP + +#include <fud_result.hpp> +#include <fud_status.hpp> +#include <fud_string.hpp> +#include <sqlite3.h> + +namespace fud { + +class SqliteStatement; + +enum class SqliteOpenMode : int +{ + ReadOnly = SQLITE_OPEN_READONLY, + ReadWrite = SQLITE_OPEN_READWRITE, + ReadWriteCreate = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE +}; + +class SqliteDb { + public: + SqliteDb(const String& name, SqliteOpenMode mode, int extraFlags = 0); + + SqliteDb(const char* name, SqliteOpenMode mode, int extraFlags = 0); + + SqliteDb(const SqliteDb&) = delete; + + SqliteDb(SqliteDb&& rhs); + + ~SqliteDb(); + + SqliteDb& operator=(const SqliteDb&) = delete; + + SqliteDb& operator=(SqliteDb&& rhs); + + bool valid() const; + + bool revalidate(); + + FudStatus exec( + const String& statement, + int (*callback)(void*, int, char**, char**), + void* context, + String* errorMessage); + + constexpr int errorCode() + { + return m_errorCode; + } + + constexpr sqlite3* handle() const + { + return m_dbHandle; + } + + // private methods + private: + void initialize(); + + [[nodiscard]] int open(); + + Result<SqliteStatement, FudStatus> prepare(const String& sql); + + // private data members + private: + String m_name{}; + + bool m_nameValid{false}; + + sqlite3* m_dbHandle{nullptr}; + + int m_errorCode{0}; + + SqliteOpenMode m_mode{}; + + int m_extraFlags{}; +}; + +class SqliteStatement { + public: + SqliteStatement(const SqliteDb& sqliteDb, const String& input); + + SqliteStatement(const SqliteStatement&) = delete; + + SqliteStatement(SqliteStatement&& rhs); + + ~SqliteStatement(); + + SqliteStatement& operator=(const SqliteStatement&) = delete; + + SqliteStatement& operator=(SqliteStatement&& rhs); + + bool valid() const; + + sqlite3_stmt* statement(); + + int step(); + + FudStatus reset(); + + constexpr int errorCode() + { + return m_errorCode; + } + + constexpr FudStatus status() + { + return m_status; + } + + private: + String m_input{}; + const char* m_tail{nullptr}; + FudStatus m_status{FudStatus::ObjectInvalid}; + int m_errorCode{0}; + sqlite3_stmt* m_preparedStatement{nullptr}; +}; + +} // namespace fud + +#endif diff --git a/include/fud_status.hpp b/include/fud_status.hpp index dd84429..6a9d653 100644 --- a/include/fud_status.hpp +++ b/include/fud_status.hpp @@ -25,6 +25,7 @@ enum class [[nodiscard]] FudStatus Success = 0, NullPointer, StringInvalid, + ObjectInvalid, OperationInvalid, AllocFailure, InvalidInput, @@ -50,6 +51,8 @@ static inline const char* FudStatusToString(FudStatus status) return "NullPointer"; case FudStatus::StringInvalid: return "StringInvalid"; + case FudStatus::ObjectInvalid: + return "ObjectInvalid"; case FudStatus::OperationInvalid: return "OperationInvalid"; case FudStatus::AllocFailure: diff --git a/source/fud_sqlite.cpp b/source/fud_sqlite.cpp new file mode 100644 index 0000000..7ad8a12 --- /dev/null +++ b/source/fud_sqlite.cpp @@ -0,0 +1,220 @@ +/* + * libfud + * Copyright 2024 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fud_sqlite.hpp" + +namespace fud { + +SqliteDb::SqliteDb(const String& name, SqliteOpenMode mode, int extraFlags) : + m_name{name}, m_mode{mode}, m_extraFlags{extraFlags} +{ + initialize(); +} + +SqliteDb::SqliteDb(const char* name, SqliteOpenMode mode, int extraFlags) : + m_name{name}, m_mode{mode}, m_extraFlags{extraFlags} +{ + initialize(); +} + +SqliteDb::SqliteDb(SqliteDb&& rhs) : + m_name{std::move(rhs.m_name)}, m_nameValid{rhs.m_nameValid}, m_dbHandle{rhs.m_dbHandle}, + m_errorCode{rhs.m_errorCode}, m_mode{rhs.m_mode}, m_extraFlags{rhs.m_extraFlags} +{ + rhs.m_nameValid = false; + rhs.m_dbHandle = nullptr; +} + +SqliteDb::~SqliteDb() +{ + if (m_dbHandle != nullptr) { + sqlite3_close(m_dbHandle); + m_dbHandle = nullptr; + } +} + +SqliteDb& SqliteDb::operator=(SqliteDb&& rhs) +{ + m_name = std::move(rhs.m_name); + m_nameValid = rhs.m_nameValid; + m_dbHandle = rhs.m_dbHandle; + m_errorCode = rhs.m_errorCode; + + rhs.m_nameValid = false; + rhs.m_dbHandle = nullptr; + + return *this; +} + +bool SqliteDb::valid() const +{ + return m_nameValid && m_dbHandle != nullptr && m_errorCode == SQLITE_OK; +} + +bool SqliteDb::revalidate() +{ + if (m_nameValid && m_dbHandle != nullptr) { + m_errorCode = SQLITE_OK; + return true; + } + return false; +} + +FudStatus SqliteDb::exec( + const String& statement, + int (*callback)(void*, int, char**, char**), + void* context, + String* errorMessage) +{ + if (!valid()) { + return FudStatus::ObjectInvalid; + } + + if (!statement.utf8Valid()) { + return FudStatus::Utf8Invalid; + } + + char* errorMsgPtr = nullptr; + char** errorMsgPtrAddress = &errorMsgPtr; + if (errorMessage == nullptr) { + errorMsgPtrAddress = nullptr; + } + + m_errorCode = sqlite3_exec( + m_dbHandle, + statement.c_str(), + callback, + context, + errorMsgPtrAddress); + + return m_errorCode == SQLITE_OK ? FudStatus::Success : FudStatus::Failure; +} + +void SqliteDb::initialize() +{ + m_nameValid = m_name.utf8Valid(); + + if (!m_nameValid) { + return; + } + + m_errorCode = open(); +} + +int SqliteDb::open() +{ + // use default vfs + return sqlite3_open_v2(m_name.c_str(), &m_dbHandle, static_cast<int>(m_mode) | m_extraFlags, nullptr); +} + +Result<SqliteStatement, FudStatus> SqliteDb::prepare(const String& dql) +{ + using RetType = Result<SqliteStatement, FudStatus>; + SqliteStatement preparedStatement{*this, dql}; + + if (!preparedStatement.valid() || preparedStatement.status() != FudStatus::Success) { + m_errorCode = preparedStatement.errorCode(); + return RetType::error(preparedStatement.status()); + } + + return RetType::okay(std::move(preparedStatement)); +} + +SqliteStatement::SqliteStatement(const SqliteDb& sqliteDb, const String& input) +{ + if (!sqliteDb.valid()) { + m_status = FudStatus::ObjectInvalid; + return; + } + + if (!input.utf8Valid() || input.length() > INT_MAX) { + m_status = FudStatus::InvalidInput; + return; + } + + int statementLength = static_cast<int>(input.length()); + m_errorCode = sqlite3_prepare_v2(sqliteDb.handle(), input.c_str(), statementLength, &m_preparedStatement, &m_tail); + if (m_errorCode == SQLITE_OK) { + m_status = FudStatus::Success; + } +} + +SqliteStatement::SqliteStatement(SqliteStatement&& rhs) : + m_input{std::move(rhs.m_input)}, m_tail{rhs.m_tail}, m_status{rhs.m_status}, m_errorCode{rhs.m_errorCode}, + m_preparedStatement{rhs.m_preparedStatement} +{ + rhs.m_tail = nullptr; + rhs.m_status = FudStatus::ObjectInvalid; + rhs.m_preparedStatement = nullptr; +} + +SqliteStatement& SqliteStatement::operator=(SqliteStatement&& rhs) +{ + m_input = std::move(rhs.m_input); + m_tail = rhs.m_tail; + m_status = rhs.m_status; + m_errorCode = rhs.m_errorCode; + m_preparedStatement = rhs.m_preparedStatement; + + rhs.m_tail = nullptr; + rhs.m_status = FudStatus::ObjectInvalid; + rhs.m_preparedStatement = nullptr; + + return *this; +} + +SqliteStatement::~SqliteStatement() +{ + if (m_preparedStatement != nullptr) { + sqlite3_finalize(m_preparedStatement); + m_preparedStatement = nullptr; + } +} + +bool SqliteStatement::valid() const +{ + return m_status == FudStatus::Success && m_preparedStatement != nullptr; +} + +sqlite3_stmt* SqliteStatement::statement() +{ + return m_preparedStatement; +} + +int SqliteStatement::step() +{ + if (!valid()) { + m_errorCode = SQLITE_MISUSE; + } else { + m_errorCode = sqlite3_step(m_preparedStatement); + } + return m_errorCode; +} + +FudStatus SqliteStatement::reset() +{ + if (!valid()) { + return FudStatus::ObjectInvalid; + } + m_errorCode = sqlite3_reset(m_preparedStatement); + if (m_errorCode != SQLITE_OK) { + return FudStatus::Failure; + } + return FudStatus::Success; +} + +} // namespace fud diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dc48e1f..73968fe 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,6 +53,7 @@ endfunction() fud_add_test(test_result SOURCES test_result.cpp) fud_add_test(test_string SOURCES test_string.cpp) +fud_add_test(test_sqlite SOURCES test_sqlite.cpp) # fud_add_test(test_deserialize_number SOURCES test_deserialize_number.cpp) # fud_add_test(test_ext_algorithm SOURCES test_algorithm.cpp) # fud_add_test(test_ext_array SOURCES diff --git a/test/test_sqlite.cpp b/test/test_sqlite.cpp new file mode 100644 index 0000000..8349324 --- /dev/null +++ b/test/test_sqlite.cpp @@ -0,0 +1,49 @@ +/* + * libfud + * Copyright 2024 Dominick Allen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "fud_sqlite.hpp" +#include "fud_string.hpp" +#include "test_common.hpp" + +#include "gtest/gtest.h" +#include <cerrno> +#include <fcntl.h> + +namespace fud { + +TEST(FudSqlite, Basic) +{ + const String testName{"test.db.sqlite"}; + auto result = unlink(testName.c_str()); + if (result != 0) { + ASSERT_EQ(errno, ENOENT); + } + + SqliteDb sqliteDb{testName, SqliteOpenMode::ReadOnly}; + ASSERT_FALSE(sqliteDb.valid()); + ASSERT_NE(sqliteDb.errorCode(), SQLITE_OK); + + sqliteDb = SqliteDb{testName, SqliteOpenMode::ReadWrite}; + ASSERT_FALSE(sqliteDb.valid()); + ASSERT_NE(sqliteDb.errorCode(), SQLITE_OK); + + sqliteDb = SqliteDb{testName, SqliteOpenMode::ReadWriteCreate}; + ASSERT_TRUE(sqliteDb.valid()); + ASSERT_EQ(sqliteDb.errorCode(), SQLITE_OK); +} + +} // namespace fud |