cpp
复制代码
#include <iostream>
#include <atomic>
#include <iomanip>
#include <functional>
#include "sqlite3/sqlite3.h"
#pragma comment(lib, "sqlite3/sqlite3.lib")
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
class Sqlite3Database {
struct PreparedStatement {
~PreparedStatement() noexcept {
sqlite3_stmt* stmt = p;
if (stmt) {
p = NULL;
sqlite3_finalize(stmt);
}
}
sqlite3_stmt* p = NULL;
};
public:
~Sqlite3Database() noexcept;
bool Open(const char* szDatabaseFilePath) noexcept;
bool IsAvailable() noexcept { return NULL != pDB_; }
std::string GetDatabaseFilePath() noexcept { return pDatabaseFilePath_; }
public:
int ExecuteNonQuery(const char* szSQL, const char** szParams, int nParamCount) noexcept;
template <typename StringIterator>
int ExecuteNonQuery(const char* szSQL, const StringIterator& itParamTail, const StringIterator& itParamEndl) noexcept;
public:
using RowHandler = std::function<bool(Sqlite3Database*, sqlite3_stmt*)>;
template <typename StringIterator>
int ExecuteQuery(const char* szSQL, const StringIterator& itParamTail, const StringIterator& itParamEndl, const RowHandler& rows) noexcept;
private:
bool Finalize(sqlite3* pDB) noexcept;
int Internal_ExecuteQuery(const char* szSQL, void* pState, bool cbFillParams(Sqlite3Database*, sqlite3_stmt*, void*), bool (*cbRow)(Sqlite3Database*, sqlite3_stmt*, void*)) noexcept;
int Internal_ExecuteNonQuery(const char* szSQL, void* pState, bool cbFillParams(Sqlite3Database*, sqlite3_stmt*, void*)) noexcept;
private:
std::atomic<sqlite3*> pDB_ = NULL;
std::string pDatabaseFilePath_;
};
Sqlite3Database::~Sqlite3Database() noexcept {
Finalize(pDB_.exchange(NULL));
}
bool Sqlite3Database::Open(const char* szDatabaseFilePath) noexcept {
if (NULL == szDatabaseFilePath || *szDatabaseFilePath == '\x0') {
return false;
}
sqlite3* pDB;
int rc = sqlite3_open(szDatabaseFilePath, &pDB);
if (rc) {
return false; // 打开数据库失败
}
Finalize(pDB_.exchange(pDB));
pDatabaseFilePath_ = szDatabaseFilePath;
return true;
}
bool Sqlite3Database::Finalize(sqlite3* pDB) noexcept {
if (NULL == pDB) {
return false;
}
if (pDB) {
sqlite3_close(pDB);
return true;
}
return false;
}
int Sqlite3Database::Internal_ExecuteNonQuery(const char* szSQL, void* pState, bool cbFillParams(Sqlite3Database*, sqlite3_stmt*, void*)) noexcept {
sqlite3* pDB = pDB_;
if (NULL == pDB) {
return -1;
}
PreparedStatement stStmt;
if (sqlite3_prepare_v2(pDB, szSQL, -1, &stStmt.p, NULL) != SQLITE_OK) {
return -1;
}
if (!cbFillParams(this, stStmt.p, pState)) {
return -1;
}
if (sqlite3_step(stStmt.p) != SQLITE_DONE) {
return -1;
}
return sqlite3_changes(pDB);
}
int Sqlite3Database::ExecuteNonQuery(const char* szSQL, const char** szParams, int nParamCount) noexcept {
if (NULL == szSQL || *szSQL == '\x0') {
return -1;
}
if (nParamCount < 0 || !szParams && nParamCount != 0) {
return -1;
}
struct ExecuteNonQueryContext {
const char** szParams;
int nParamCount;
} stExecuteNonQueryContext;
stExecuteNonQueryContext.szParams = szParams;
stExecuteNonQueryContext.nParamCount = nParamCount;
return Internal_ExecuteNonQuery(szSQL, &stExecuteNonQueryContext,
[](Sqlite3Database* pDB, sqlite3_stmt* pStmt, void* p) noexcept {
ExecuteNonQueryContext* pCtx = static_cast<ExecuteNonQueryContext*>(p);
for (int i = 0; i < pCtx->nParamCount; i++) {
const char* szParam = pCtx->szParams[i];
if (!szParam) {
szParam = "";
}
if (sqlite3_bind_text(pStmt, i + 1, szParam, -1, SQLITE_STATIC) != SQLITE_OK) {
return false;
}
}
return true;
});
}
template <typename StringIterator>
inline
int Sqlite3Database::ExecuteNonQuery(const char* szSQL, const StringIterator& itParamTail, const StringIterator& itParamEndl) noexcept {
if (NULL == szSQL || *szSQL == '\x0') {
return -1;
}
struct ExecuteNonQueryContext {
StringIterator itTail;
StringIterator itEndl;
} stExecuteNonQueryContext{ itParamTail, itParamEndl };
return Internal_ExecuteNonQuery(szSQL, &stExecuteNonQueryContext,
[](Sqlite3Database*, sqlite3_stmt* pStmt, void* p) noexcept -> bool {
auto* pCtx = static_cast<ExecuteNonQueryContext*>(p);
int iParamIndex = 1;
for (auto it = pCtx->itTail; it != pCtx->itEndl; ++it) {
const char* szParam = *it;
if (!szParam) {
szParam = "";
}
if (sqlite3_bind_text(pStmt, iParamIndex++, szParam, -1, SQLITE_STATIC) != SQLITE_OK) {
return false;
}
}
return true;
});
}
int Sqlite3Database::Internal_ExecuteQuery(const char* szSQL, void* pState, bool cbFillParams(Sqlite3Database*, sqlite3_stmt*, void*), bool (*cbRow)(Sqlite3Database*, sqlite3_stmt*, void*)) noexcept {
sqlite3* pDB = pDB_;
if (NULL == pDB) {
return -1;
}
PreparedStatement stStmt;
int rc = sqlite3_prepare_v2(pDB, szSQL, -1, &stStmt.p, NULL);
if (rc != SQLITE_OK) {
return -1;
}
if (!cbFillParams(this, stStmt.p, pState)) {
return -1;
}
int iRowCount = 0;
while (sqlite3_step(stStmt.p) == SQLITE_ROW) {
if (!cbRow(this, stStmt.p, pState)) {
break; // 用户要求停止
}
++iRowCount;
}
return iRowCount; // 返回实际读取的行数
}
template <typename StringIterator>
inline
int Sqlite3Database::ExecuteQuery(const char* szSQL, const StringIterator& itParamTail, const StringIterator& itParamEndl, const RowHandler& rows) noexcept {
if (NULL == szSQL || *szSQL == '\x0') {
return -1;
}
if (!rows) {
return -1;
}
struct ExecuteQueryContext {
StringIterator itParamTail;
StringIterator itParamEndl;
RowHandler fnRows;
} stExecuteQueryContext;
stExecuteQueryContext.fnRows = rows;
stExecuteQueryContext.itParamTail = itParamTail;
stExecuteQueryContext.itParamEndl = itParamEndl;
auto fnFillParams =
[](Sqlite3Database* pDB, sqlite3_stmt* pStmt, void* p) noexcept -> bool {
ExecuteQueryContext* pCtx = static_cast<ExecuteQueryContext*>(p);
for (int iParamIndex = 1; pCtx->itParamTail != pCtx->itParamTail; ++pCtx->itParamTail) {
const char* szParam = *pCtx->itParamEndl;
if (!szParam) {
szParam = "";
}
if (sqlite3_bind_text(pStmt, iParamIndex++, szParam, -1, SQLITE_STATIC) != SQLITE_OK) {
return false;
}
}
return true;
};
auto fnRows =
[](Sqlite3Database* pDB, sqlite3_stmt* pStmt, void* p) noexcept -> bool {
ExecuteQueryContext* pCtx = static_cast<ExecuteQueryContext*>(p);
return pCtx->fnRows(pDB, pStmt);
};
return Internal_ExecuteQuery(szSQL, &stExecuteQueryContext, fnFillParams, fnRows);
}
// ----------------------------------------------------------------------
// Demo
// ----------------------------------------------------------------------
int main() {
Sqlite3Database db;
if (!db.Open("demo.db")) {
std::cerr << "Failed to open database" << std::endl;
return 1;
}
// Drop old table to ensure clean state
db.ExecuteNonQuery("DROP TABLE IF EXISTS users;", nullptr, 0);
// Create table
const char* createSQL =
"CREATE TABLE IF NOT EXISTS users ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"name TEXT NOT NULL,"
"age INTEGER"
");";
if (db.ExecuteNonQuery(createSQL, nullptr, 0) < 0) {
std::cerr << "Failed to create table" << std::endl;
return 1;
}
// Insert data
std::vector<const char*> insertParams = { "Alice", "25" };
int affected = db.ExecuteNonQuery(
"INSERT INTO users (name, age) VALUES (?, ?);",
insertParams.begin(), insertParams.end()
);
std::cout << "Rows inserted: " << affected << std::endl;
insertParams = { "Bob", "30" };
affected = db.ExecuteNonQuery(
"INSERT INTO users (name, age) VALUES (?, ?);",
insertParams.begin(), insertParams.end()
);
std::cout << "Rows inserted: " << affected << std::endl;
// ------------------------------------------------------------------
// Dynamic printer: automatically detects column names and types
// ------------------------------------------------------------------
auto printRowDynamic = [](Sqlite3Database*, sqlite3_stmt* stmt) -> bool {
static bool headerPrinted = false;
int colCount = sqlite3_column_count(stmt);
if (!headerPrinted) {
for (int i = 0; i < colCount; ++i) {
const char* colName = sqlite3_column_name(stmt, i);
std::cout << std::setw(15) << (colName ? colName : "?");
if (i != colCount - 1) std::cout << " ";
}
std::cout << std::endl;
for (int i = 0; i < colCount; ++i) {
std::cout << std::setw(15) << "-----------------";
if (i != colCount - 1) std::cout << " ";
}
std::cout << std::endl;
headerPrinted = true;
}
for (int i = 0; i < colCount; ++i) {
int type = sqlite3_column_type(stmt, i);
switch (type) {
case SQLITE_INTEGER:
std::cout << std::setw(15) << sqlite3_column_int64(stmt, i);
break;
case SQLITE_FLOAT:
std::cout << std::setw(15) << sqlite3_column_double(stmt, i);
break;
case SQLITE_TEXT: {
const char* text = (const char*)sqlite3_column_text(stmt, i);
std::cout << std::setw(15) << (text ? text : "NULL");
break;
}
case SQLITE_BLOB:
std::cout << std::setw(15) << "[BLOB]";
break;
case SQLITE_NULL:
std::cout << std::setw(15) << "NULL";
break;
default:
std::cout << std::setw(15) << "?";
break;
}
if (i != colCount - 1) std::cout << " ";
}
std::cout << std::endl;
return true;
};
// Query all
std::cout << "\nAll users (dynamic printer):" << std::endl;
int rowCount = db.ExecuteQuery(
"SELECT id, name, age FROM users;",
std::vector<const char*>{}.begin(),
std::vector<const char*>{}.end(),
printRowDynamic
);
std::cout << "Query returned " << rowCount << " rows" << std::endl;
// Conditional query
std::cout << "\nUsers with age >= 25:" << std::endl;
std::vector<const char*> queryParams = { "25" };
rowCount = db.ExecuteQuery(
"SELECT id, name, age FROM users WHERE age >= ?;",
queryParams.begin(), queryParams.end(),
printRowDynamic
);
std::cout << "Query returned " << rowCount << " rows" << std::endl;
// Update
std::vector<const char*> updateParams = { "26", "Alice" };
affected = db.ExecuteNonQuery(
"UPDATE users SET age = ? WHERE name = ?;",
updateParams.begin(), updateParams.end()
);
std::cout << "\nRows updated: " << affected << std::endl;
// Verify update
std::cout << "\nAll users after update:" << std::endl;
db.ExecuteQuery(
"SELECT id, name, age FROM users;",
std::vector<const char*>{}.begin(),
std::vector<const char*>{}.end(),
printRowDynamic
);
// Delete
std::vector<const char*> deleteParams = { "Bob" };
affected = db.ExecuteNonQuery(
"DELETE FROM users WHERE name = ?;",
deleteParams.begin(), deleteParams.end()
);
std::cout << "\nRows deleted: " << affected << std::endl;
// Final users
std::cout << "\nRemaining users:" << std::endl;
db.ExecuteQuery(
"SELECT id, name, age FROM users;",
std::vector<const char*>{}.begin(),
std::vector<const char*>{}.end(),
printRowDynamic
);
return 0;
}