为什么 SQLite3 lib 对 sqlite3_stmt 抛出异常?

问题描述 投票:0回答:1

我有

SqliteDatabaseManager
类来封装所有数据库交互。这是它的界面:

#pragma once

#include <string>
#include <string_view>
#include <vector>
#include <memory>

class IQueryResult
{
public:
    virtual ~IQueryResult() = default;

    virtual bool next() = 0;
    virtual std::string getString(int columnIndex) const = 0;
};

/**
 * \brief Interface for databases like SQLite or Postgre
 *
 * Example of usage:
 *  executeQuery("INSERT INTO Users (username, password) VALUES (?, ?);", {"username1", "password1"});
 */
class IDatabaseManager
{
public:
    virtual ~IDatabaseManager() = default;

    virtual bool open() = 0;
    virtual void close() = 0;
    virtual std::shared_ptr<IQueryResult> executeQuery(std::string_view query, const std::vector<std::string> &params) = 0;
};

所以,我的

SqliteDatabaseManager.cpp

  1. SqliteQueryResult
    封装了与
    sqlite3_stmt
  2. 的交互
  3. SqliteDatabaseManager
    open()
    数据库、
    executeQuery
    close()
  4. 执行所有操作

我已经使用 pimpl 实现了所有交互:

#include <iostream>
#include "SqliteDatabaseManager.hpp"

#include <filesystem>
#include <fstream>
#include <memory>
#include <sstream>
#include <vector>
#include <string_view>

#include <sqlite3.h>

#define _M "SqliteDatabaseManager"

class SqliteQueryResult : public IQueryResult
{
    std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)> stmt;

public:
    SqliteQueryResult(sqlite3_stmt *stmtPtr)
        : stmt(stmtPtr, sqlite3_finalize) {}

    bool next() override
    {
        int stepResult = sqlite3_step(stmt.get());
        if (stepResult == SQLITE_ROW)
        {
            return true;
        }
        else if (stepResult == SQLITE_DONE)
        {
            return false;
        }
        else
        {
            const std::string errorMsg = "SQL error: " + std::string(sqlite3_errmsg(sqlite3_db_handle(stmt.get())));
            std::cerr << errorMsg << std::endl;
            return false;
        }
    }

    std::string getString(int columnIndex) const override
    {
        auto text = sqlite3_column_text(stmt.get(), columnIndex);
        return text ? reinterpret_cast<const char *>(text) : "";
    }
};

class SqliteDatabaseManager::Impl
{
public:
    std::string m_dbPath, m_createDatabaseScriptPath;
    std::unique_ptr<sqlite3, decltype(&sqlite3_close)> m_db{nullptr, sqlite3_close};

    Impl(std::string_view path, std::string_view createDatabaseScriptPath)
        : m_dbPath(path),
          m_createDatabaseScriptPath(createDatabaseScriptPath) {}

    bool open()
    {
        const auto fileExists = std::filesystem::exists(m_dbPath); // Use C++17 std::filesystem for file existence check

        sqlite3 *dbRaw = nullptr;
        if (sqlite3_open(m_dbPath.c_str(), &dbRaw) != SQLITE_OK)
        {
            std::cerr << "Failed to open database: " << sqlite3_errmsg(dbRaw) << std::endl;
            if (dbRaw)
                sqlite3_close(dbRaw);
            return false;
        }

        m_db.reset(dbRaw);

        if (!fileExists && !initializeDatabaseSchema())
        {
            std::cerr << "Failed to initialize database schema." << std::endl;
            return false;
        }

        std::cout << "Database opened successfully" << std::endl;
        return true;
    }

    void close()
    {
        m_db.reset();
        std::cout << "Database closed successfully" << std::endl;
    }

    std::shared_ptr<IQueryResult> executeQuery(std::string_view query, const std::vector<std::string> &params)
    {
        std::cerr << "SQLiteDatabaseManager::executeQuery" << std::endl;
        std::cerr << "SQLiteDatabaseManager::executeQuery | query = \"" << query << "\"" << std::endl;

        sqlite3_stmt *stmtRaw;
        if (sqlite3_prepare_v2(m_db.get(), query.data(), -1, &stmtRaw, nullptr) != SQLITE_OK)
        {
            const std::string errorMsg = "Failed to prepare statement: " + std::string(sqlite3_errmsg(m_db.get()));
            std::cerr << errorMsg << std::endl;
        }

        std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)> stmtGuard(stmtRaw, sqlite3_finalize);

        // std::string debugQuery = query.data();
        for (size_t i = 0; i < params.size(); ++i)
        {
            if (sqlite3_bind_text(stmtRaw, static_cast<int>(i + 1), params[i].c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
            {
                const std::string errorMsg = "Failed to bind parameter at position " + std::to_string(i + 1) + ": " + sqlite3_errmsg(m_db.get());
                std::cerr << errorMsg << std::endl;
            }

            // Note: Final query for debugging purposes
            // auto pos = debugQuery.find('?');
            // if (pos != std::string::npos)
            // {
            //     debugQuery.replace(pos, 1, "'" + params[i] + "'");
            // }
        }

        // std::cerr << "SQLiteDatabaseManager::executeQuery | final query = \"" << debugQuery << "\"" << std::endl;

        return std::make_shared<SqliteQueryResult>(stmtRaw);
    }

    bool initializeDatabaseSchema()
    {
        std::cerr << "SQLiteDatabaseManager::initializeDatabaseSchema" << std::endl;
        std::cerr << "Initializing database schema | using sql script: " << m_createDatabaseScriptPath << std::endl;

        if (!std::filesystem::exists(m_createDatabaseScriptPath))
        {
            std::cerr << "Could not open SQL script file: " << m_createDatabaseScriptPath << std::endl;
            return false;
        }

        std::ifstream sqlFile(m_createDatabaseScriptPath);
        std::string sqlCommands((std::istreambuf_iterator<char>(sqlFile)),
                                std::istreambuf_iterator<char>());

        char *errMsg = nullptr;
        if (sqlite3_exec(m_db.get(), sqlCommands.c_str(), nullptr, nullptr, &errMsg) != SQLITE_OK)
        {
            std::cerr << "Initialize database schema error: " << errMsg << std::endl;
            sqlite3_free(errMsg);
            return false;
        }

        std::cout << "Database schema initialized successfully" << std::endl;
        return true;
    }
};

SqliteDatabaseManager::SqliteDatabaseManager(std::string_view dbPath, std::string_view createDatabaseScriptPath)
    : pImpl(std::make_unique<Impl>(dbPath, createDatabaseScriptPath)) {}

SqliteDatabaseManager::~SqliteDatabaseManager() = default;

bool SqliteDatabaseManager::open()
{
    return pImpl->open();
}

void SqliteDatabaseManager::close()
{
    pImpl->close();
}

std::shared_ptr<IQueryResult> SqliteDatabaseManager::executeQuery(std::string_view query, const std::vector<std::string> &params)
{
    return pImpl->executeQuery(query, params);
}

然后我就简单地使用它:

#include "SqliteDatabaseManager.hpp"

int main()
{
    SqliteDatabaseManager db(DB_PATH, SQL_FILE);
    db.open();
    db.executeQuery("SELECT id, username, password, roleId FROM Users WHERE username = ?;", {"admin"});
    db.close();
    return 0;
}

当然,我已经使用 SQLiteStudio 检查了查询正确性和数据库有效性:

但这给了我例外

有时会出现在这里:

std::shared_ptr<IQueryResult> SqliteDatabaseManager::executeQuery(std::string_view query, const std::vector<std::string> &params)
{
    return pImpl->executeQuery(query, params); // <<<--- here
}

有时在这里:

    bool next() override
    {
        int stepResult = sqlite3_step(stmt.get()); // <<<--- here
        if (stepResult == SQLITE_ROW)
        {
            return true;
        }
        else if (stepResult == SQLITE_DONE)
        {
            return false;
        }
        else
        {
            const std::string errorMsg = "SQL error: " + std::string(sqlite3_errmsg(sqlite3_db_handle(stmt.get())));
            std::cerr << errorMsg << std::endl;
            return false;
        }
    }

尝试过调试和日志记录。

日志

Database opened successfully
SQLiteDatabaseManager::executeQuery
SQLiteDatabaseManager::executeQuery | query = "SELECT id, username, password, roleId FROM Users WHERE username = ?;"
SqliteQueryResult::SqliteQueryResult

 *  The terminal process
c++ sqlite cmake
1个回答
0
投票

我刚刚忘记初始化

stmtRaw
。畏缩。

executeQuery
的固定变体:

   std::shared_ptr<IQueryResult> executeQuery(std::string_view query, const std::vector<std::string> &params)
    {
        std::cerr << "SQLiteDatabaseManager::executeQuery" << std::endl;
        std::cerr << "SQLiteDatabaseManager::executeQuery | query = \"" << query << "\"" << std::endl;

        sqlite3_stmt *stmtRaw = nullptr;
        if (sqlite3_prepare_v2(m_db.get(), query.data(), -1, &stmtRaw, nullptr) != SQLITE_OK)
        {
            const std::string errorMsg = "Failed to prepare statement: " + std::string(sqlite3_errmsg(m_db.get()));
            std::cerr << errorMsg << std::endl;
            return nullptr;
        }

        // Ensure stmtRaw is valid before proceeding
        if (!stmtRaw)
        {
            std::cerr << "Failed to create the statement, stmtRaw is null." << std::endl;
            return nullptr;
        }

        // Binding parameters
        for (size_t i = 0; i < params.size(); ++i)
        {
            if (sqlite3_bind_text(stmtRaw, static_cast<int>(i + 1), params[i].c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
            {
                const std::string errorMsg = "Failed to bind parameter at position " + std::to_string(i + 1) + ": " + sqlite3_errmsg(m_db.get());
                std::cerr << errorMsg << std::endl;
                sqlite3_finalize(stmtRaw); // Clean up before returning
                return nullptr;
            }
        }

        return std::make_shared<SqliteQueryResult>(stmtRaw);
    }
© www.soinside.com 2019 - 2024. All rights reserved.