fix(libsqlite3): 增加参数化 exec 与完整绑定/读取接口

- 新增模板 exec(sql, args...) 参数化执行,支持 int / int64_t / double /
  std::string / const char* / nullptr / std::vector<unsigned char>
- 扩展 Sqlite3_stmt:bind_int64、bind_double、bind_blob、bind_null、execute
- 增加 read_int64、read_double,read_text 补充生命周期注释
- 构造/打开异常携带 sqlite3_errmsg 详情
- 原 exec(sql) 增加硬编码 SQL 警告注释
This commit is contained in:
qing committed 2026-08-06 18:13:52 +00:00
1 parent 1c55443d43
commit 48714b88af
1 file changed
+91 -7
+91 -7
View File
@@ -1,6 +1,10 @@
#include <cstdint>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <sqlite3.h> #include <sqlite3.h>
#include <string>
#include <type_traits>
#include <vector>
namespace qing { namespace qing {
@@ -9,8 +13,9 @@ namespace qing {
Sqlite3_stmt(sqlite3 *db, const char *sql) { Sqlite3_stmt(sqlite3 *db, const char *sql) {
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
std::cerr << "Failed to prepare sql stmt: " << sqlite3_errmsg(db) << std::endl; std::string err = sqlite3_errmsg(db);
throw std::runtime_error("Failed to prepare sql stmt"); std::cerr << "Failed to prepare sql stmt: " << err << std::endl;
throw std::runtime_error("Failed to prepare sql stmt: " + err);
} }
} }
@@ -40,19 +45,45 @@ namespace qing {
void bind_int(int idx, int data) { void bind_int(int idx, int data) {
int rc = sqlite3_bind_int(stmt, idx, data); int rc = sqlite3_bind_int(stmt, idx, data);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
std::cerr << "Failed to bind an integer to sql stmt." << std::endl;
throw std::runtime_error("Failed to bind an integer to sql stmt"); throw std::runtime_error("Failed to bind an integer to sql stmt");
} }
} }
void bind_int64(int idx, int64_t data) {
int rc = sqlite3_bind_int64(stmt, idx, data);
if (rc != SQLITE_OK) {
throw std::runtime_error("Failed to bind an int64 to sql stmt");
}
}
void bind_double(int idx, double data) {
int rc = sqlite3_bind_double(stmt, idx, data);
if (rc != SQLITE_OK) {
throw std::runtime_error("Failed to bind a double to sql stmt");
}
}
void bind_text(int idx, const std::string &data) { void bind_text(int idx, const std::string &data) {
int rc = sqlite3_bind_text(stmt, idx, data.c_str(), -1, SQLITE_TRANSIENT); int rc = sqlite3_bind_text(stmt, idx, data.c_str(), -1, SQLITE_TRANSIENT);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
std::cerr << "Failed to bind a text to sql stmt." << std::endl;
throw std::runtime_error("Failed to bind a text to sql stmt"); throw std::runtime_error("Failed to bind a text to sql stmt");
} }
} }
void bind_blob(int idx, const std::vector<unsigned char>& data) {
int rc = sqlite3_bind_blob(stmt, idx, data.data(), static_cast<int>(data.size()), SQLITE_TRANSIENT);
if (rc != SQLITE_OK) {
throw std::runtime_error("Failed to bind a blob to sql stmt");
}
}
void bind_null(int idx) {
int rc = sqlite3_bind_null(stmt, idx);
if (rc != SQLITE_OK) {
throw std::runtime_error("Failed to bind null to sql stmt");
}
}
bool step() { bool step() {
int rc = sqlite3_step(stmt); int rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW && rc != SQLITE_DONE) { if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
@@ -61,6 +92,15 @@ namespace qing {
return rc == SQLITE_ROW && rc != SQLITE_DONE; return rc == SQLITE_ROW && rc != SQLITE_DONE;
} }
// 执行无结果集语句(INSERT / UPDATE / DELETE / DDL),返回影响行数
int execute() {
int rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
throw std::runtime_error("Failed to execute statement");
}
return sqlite3_changes(sqlite3_db_handle(stmt));
}
/* /*
* int len = sqlite3_column_bytes(stmt, 1); // 获取字节长度(不含结束符) * int len = sqlite3_column_bytes(stmt, 1); // 获取字节长度(不含结束符)
* std::string name(reinterpret_cast<const char*>(rawName), len); * std::string name(reinterpret_cast<const char*>(rawName), len);
@@ -69,7 +109,16 @@ namespace qing {
return sqlite3_column_int(stmt, idx); return sqlite3_column_int(stmt, idx);
} }
int64_t read_int64(int idx) {
return sqlite3_column_int64(stmt, idx);
}
double read_double(int idx) {
return sqlite3_column_double(stmt, idx);
}
// 返回指向 statement 内部缓冲区的指针,生命周期与当前 statement 绑定。
// statement finalize 或 step 后指针可能失效;如需长期保存请复制到 std::string。
const unsigned char *read_text(int idx) { const unsigned char *read_text(int idx) {
return sqlite3_column_text(stmt, idx); return sqlite3_column_text(stmt, idx);
} }
@@ -84,9 +133,10 @@ namespace qing {
Sqlite3(const std::string& filename) { Sqlite3(const std::string& filename) {
int rc = sqlite3_open(filename.c_str(), &db); int rc = sqlite3_open(filename.c_str(), &db);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
std::cerr << "Failed to open database: " << sqlite3_errmsg(db) << std::endl; std::string err = sqlite3_errmsg(db);
std::cerr << "Failed to open database: " << err << std::endl;
sqlite3_close(db); sqlite3_close(db);
throw std::runtime_error("Failed to open sqlite3 database."); throw std::runtime_error("Failed to open sqlite3 database: " + err);
} }
} }
@@ -113,11 +163,13 @@ namespace qing {
return *this; return *this;
} }
// 仅用于硬编码 SQL;禁止拼接用户输入!
bool exec(const std::string& sql) { bool exec(const std::string& sql) {
char *errMsg = nullptr; char *errMsg = nullptr;
int rc = sqlite3_exec(db, sql.c_str(), 0, 0, &errMsg); int rc = sqlite3_exec(db, sql.c_str(), 0, 0, &errMsg);
if (rc != SQLITE_OK) { if (rc != SQLITE_OK) {
std::cerr << "Failed to execute at sqlite3: " << errMsg << std::endl; std::string err = errMsg ? errMsg : "unknown error";
std::cerr << "Failed to execute at sqlite3: " << err << std::endl;
sqlite3_free(errMsg); sqlite3_free(errMsg);
return false; return false;
} }
@@ -125,11 +177,43 @@ namespace qing {
return true; return true;
} }
// 参数化执行 INSERT / UPDATE / DELETE / DDL,支持 int / int64_t / double /
// std::string / const char* / nullptr / std::vector<unsigned char>
template<typename... Args>
int exec(const std::string& sql, Args&&... args) {
Sqlite3_stmt s(db, sql.c_str());
int idx = 1;
(_bind_one(s, idx++, std::forward<Args>(args)), ...);
return s.execute();
}
Sqlite3_stmt stmt(const std::string& sql) { Sqlite3_stmt stmt(const std::string& sql) {
return Sqlite3_stmt(db, sql.c_str()); return Sqlite3_stmt(db, sql.c_str());
} }
private: private:
sqlite3* db = nullptr; sqlite3* db = nullptr;
template<typename T>
static void _bind_one(Sqlite3_stmt& s, int idx, T&& val) {
using U = std::decay_t<T>;
if constexpr (std::is_same_v<U, std::nullptr_t>) {
s.bind_null(idx);
} else if constexpr (std::is_same_v<U, int> || std::is_same_v<U, short> || std::is_same_v<U, bool>) {
s.bind_int(idx, static_cast<int>(val));
} else if constexpr (std::is_same_v<U, int64_t> || std::is_same_v<U, long> || std::is_same_v<U, long long>) {
s.bind_int64(idx, static_cast<int64_t>(val));
} else if constexpr (std::is_same_v<U, double> || std::is_same_v<U, float>) {
s.bind_double(idx, static_cast<double>(val));
} else if constexpr (std::is_same_v<U, std::string>) {
s.bind_text(idx, val);
} else if constexpr (std::is_same_v<U, const char*>) {
s.bind_text(idx, std::string(val));
} else if constexpr (std::is_same_v<U, std::vector<unsigned char>>) {
s.bind_blob(idx, val);
} else {
static_assert(!std::is_same_v<U, U>, "unsupported bind type");
}
}
}; };
} }