/* * 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 { SqliteDbResult SqliteDb::make(const String& name, SqliteOpenMode mode, int extraFlags) { SqliteDb sqlDb{}; auto copyResult = sqlDb.m_name.copy(name); if (copyResult != FudStatus::Success) { return SqliteDbResult::error(copyResult); } return finishMake(mode, extraFlags, std::move(sqlDb)); } SqliteDbResult SqliteDb::make(StringView name, SqliteOpenMode mode, int extraFlags) { SqliteDb sqlDb{}; auto nameResult{String::from(name)}; if (nameResult.isError()) { return SqliteDbResult::error(nameResult.takeError()); } sqlDb.m_name = nameResult.takeOkay(); return finishMake(mode, extraFlags, std::move(sqlDb)); } SqliteDbResult SqliteDb::make(const char* cStrName, SqliteOpenMode mode, int extraFlags) { SqliteDb sqlDb{}; auto nameResult = String::makeFromCString(cStrName); if (nameResult.isError()) { return SqliteDbResult::error(nameResult); } sqlDb.m_name = nameResult.takeOkay(); return finishMake(mode, extraFlags, std::move(sqlDb)); } SqliteDbResult SqliteDb::finishMake(SqliteOpenMode& mode, int& extraFlags, SqliteDb&& sqlDb) { sqlDb.m_mode = mode; sqlDb.m_extraFlags = extraFlags; auto status = sqlDb.initialize(); if (status != FudStatus::Success) { return SqliteDbResult::error(status); } return SqliteDbResult::okay(std::move(sqlDb)); } SqliteDb::SqliteDb(SqliteDb&& rhs) noexcept : 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) noexcept { if (m_dbHandle != nullptr) { sqlite3_close(m_dbHandle); m_dbHandle = nullptr; } 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; return *this; } bool SqliteDb::valid() const { return m_nameValid && m_dbHandle != nullptr && (m_errorCode == SQLITE_OK || m_errorCode == SQLITE_DONE); } bool SqliteDb::revalidate() { if (m_nameValid && m_dbHandle != nullptr) { m_errorCode = SQLITE_OK; return true; } return false; } FudStatus SqliteDb::initialize() { m_nameValid = m_name.utf8Valid(); if (!m_nameValid) { return FudStatus::ArgumentInvalid; } m_errorCode = open(); if (m_errorCode != SQLITE_OK) { return FudStatus::Failure; } return FudStatus::Success; } int SqliteDb::open() { // use default vfs return sqlite3_open_v2(m_name.c_str(), &m_dbHandle, static_cast(m_mode) | m_extraFlags, nullptr); } FudStatus SqliteDb::exec(const String& statement, int (*callback)(void*, int, char**, char**), void* context) { if (!valid()) { return FudStatus::ObjectInvalid; } if (!statement.utf8Valid()) { return FudStatus::Utf8Invalid; } m_errorCode = sqlite3_exec(m_dbHandle, statement.c_str(), callback, context, nullptr); return m_errorCode == SQLITE_OK ? FudStatus::Success : FudStatus::Failure; } FudStatus SqliteDb::exec( const String& statement, int (*callback)(void*, int, char**, char**), void* context, SqliteErrorMsg& errorMessage) { if (!valid()) { return FudStatus::ObjectInvalid; } char* errorMsgPtr = nullptr; if (!statement.utf8Valid()) { return FudStatus::Utf8Invalid; } m_errorCode = sqlite3_exec(m_dbHandle, statement.c_str(), callback, context, &errorMsgPtr); errorMessage.setMessage(errorMsgPtr); return m_errorCode == SQLITE_OK ? FudStatus::Success : FudStatus::Failure; } FudStatus SqliteDb::step(SqliteStatement& statement) { if (!statement.valid()) { m_errorCode = SQLITE_MISUSE; } else { m_errorCode = sqlite3_step(statement.statement()); } switch (m_errorCode) { case SQLITE_OK: case SQLITE_ROW: return FudStatus::Success; case SQLITE_DONE: return FudStatus::NotFound; default: return FudStatus::Failure; } } Result SqliteDb::columnInt32(SqliteStatement& statement, int index) { if (!statement.valid() || statement.status() != FudStatus::Success) { m_errorCode = statement.errorCode(); return Error{statement.status()}; } if (index < 0) { return Error{FudStatus::ArgumentInvalid}; } if (sqlite3_column_type(statement.statement(), index) != SQLITE_INTEGER) { return Error{FudStatus::ArgumentInvalid}; } auto output = sqlite3_column_int(statement.statement(), index); return Okay{output}; } Result SqliteDb::columnInt64(SqliteStatement& statement, int index) { if (!statement.valid() || statement.status() != FudStatus::Success) { m_errorCode = statement.errorCode(); return Error{statement.status()}; } if (index < 0) { return Error{FudStatus::ArgumentInvalid}; } if (sqlite3_column_type(statement.statement(), index) != SQLITE_INTEGER) { return Error{FudStatus::ArgumentInvalid}; } auto output = sqlite3_column_int64(statement.statement(), index); static_assert(std::is_convertible_v); return Okay{static_cast(output)}; } Result SqliteDb::columnDouble(SqliteStatement& statement, int index) { if (!statement.valid() || statement.status() != FudStatus::Success) { m_errorCode = statement.errorCode(); return Error{statement.status()}; } if (index < 0) { return Error{FudStatus::ArgumentInvalid}; } if (sqlite3_column_type(statement.statement(), index) != SQLITE_FLOAT) { return Error{FudStatus::ArgumentInvalid}; } auto output = sqlite3_column_double(statement.statement(), index); return Okay{output}; } Result SqliteDb::columnText(SqliteStatement& statement, int index) { if (!statement.valid() || statement.status() != FudStatus::Success) { m_errorCode = statement.errorCode(); return Error{statement.status()}; } if (index < 0) { return Error{FudStatus::ArgumentInvalid}; } StringView output{}; if (sqlite3_column_type(statement.statement(), index) != SQLITE_TEXT) { return Error{FudStatus::ArgumentInvalid}; } auto outputLength = sqlite3_column_bytes(statement.statement(), index); if (outputLength < 0) { return Error{FudStatus::Failure}; } output.m_length = static_cast(outputLength); output.m_data = std::bit_cast(sqlite3_column_text(statement.statement(), index)); if (output.m_data == nullptr && output.m_length != 0) { return Error{FudStatus::Failure}; } return Okay{output}; } Result SqliteDb::prepare(const String& sql) { SqliteStatement preparedStatement{*this, sql}; if (!preparedStatement.valid() || preparedStatement.status() != FudStatus::Success) { m_errorCode = preparedStatement.errorCode(); return Error{preparedStatement.status()}; } return 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::ArgumentInvalid; return; } int statementLength = static_cast(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) noexcept : 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() { 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; } 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; } SqliteErrorMsg::SqliteErrorMsg(SqliteErrorMsg&& rhs) noexcept : m_errorMsg{rhs.m_errorMsg} { rhs.m_errorMsg = nullptr; } SqliteErrorMsg& SqliteErrorMsg::operator=(SqliteErrorMsg&& rhs) noexcept { if (m_errorMsg != nullptr) { sqlite3_free(m_errorMsg); m_errorMsg = nullptr; } m_errorMsg = rhs.m_errorMsg; rhs.m_errorMsg = nullptr; return *this; } SqliteErrorMsg::~SqliteErrorMsg() { if (m_errorMsg != nullptr) { sqlite3_free(m_errorMsg); m_errorMsg = nullptr; } } void SqliteErrorMsg::setMessage(char* newMessage) { if (m_errorMsg != nullptr) { sqlite3_free(m_errorMsg); } m_errorMsg = newMessage; } } // namespace fud