我有
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> ¶ms) = 0;
};
所以,我的
SqliteDatabaseManager.cpp
:
SqliteQueryResult
封装了与 sqlite3_stmt
SqliteDatabaseManager
对 open()
数据库、executeQuery
和 close()
我已经使用 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> ¶ms)
{
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> ¶ms)
{
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> ¶ms)
{
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
我刚刚忘记初始化
stmtRaw
。畏缩。
executeQuery
的固定变体:
std::shared_ptr<IQueryResult> executeQuery(std::string_view query, const std::vector<std::string> ¶ms)
{
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);
}